[Python] 07 - Statements --> Functions

故事背景


  • 阶级关系

1. Programs are composed of modules.
2. Modules contain statements.
3. Statements contain expressions.
4. Expressions create and process objects.

 

  • 教学大纲 

 

 

 

  

 

Statements


大纲纵览  

Python 3.X’s statements

  • Python 3.X reserved words.

 

  • 一些简单的示范例子,page 372/1594
 1 a, b = 'good', 'bad'
 2 
 3 log.write("spam, ham")
 4 
 5 print('Then Killer', joke)
 6 
 7 if "python" in text:
 8   print(text)
 9 
10 for x in mylist:
11   print(x)
12 
13 while X > Y:
14   print('hello')
15 
16 while True:
17   pass
18 
19 while True:
20   if exittest(): break
21 
22 while True:
23   if skiptest(): continue
24 
25 def f(a, b, c=1, *d):
26   print(a+b+c+d[0])
27 
28 # 其中一个*号代表list或者tuple,**代表map或者dict
29 def f(a, b, c=1, *d):
30   return(a+b+c+d[0])
31 
32 def gen(n):
33   for i in n:
34     yield i*2
35 
36 x = 'old'
37 def function():
38   global x, y:
39   x = 'new'
40 
41 # https://stackoverflow.com/questions/1261875/python-nonlocal-statement
42 def outer():
43   x = 'old'
44   def function():
45     nonlocal x:
46     x = 'new'
47 
48 import sys
49 
50 from sys import stdin
51 
52 class Subclass(Superclass):
53   staticData = []
54   def method(self):
55     pass
56 
57 try:
58   action()
59 except:
60   print('action error')
61 
62 # 需进一步理解
63 raise EndSearch(location)
64 
65 assert X > Y, 'X too small'
66 
67 with open('data') as myfile:
68   process(myfile)
69 
70 del data[k]
71 del data[i:j]
72 del obj.attr
73 del variable
View Code 

一些值得注意的用法.

28 # 其中一个*号代表list或者tuple,**代表map或者dict
29 def f(a, b, c=1, *d):
30   return(a+b+c+d[0])
31 
32 def gen(n):
33   for i in n:
34     yield i*2
35 
36 x = 'old'
37 def function():
38   global x, y:
39   x = 'new'
40 
41 # https://stackoverflow.com/questions/1261875/python-nonlocal-statement
42 def outer():
43   x = 'old'
44   def function():
45     nonlocal x:
46     x = 'new'

70 del data[k]
71 del data[i:j]
72 del obj.attr
73 del variable

 

  • Closure 和 Non-local 的迷惑 

大函数返回小函数小函数仅能"只读"大函数的局部变量。

如果“可写”,则需要Non-local。

 

 

变量初始化

Level 1 - 弱初始化

 

Level 2 - 配对赋值

>>> A, B = nudge, wink        # Tuple assignment

>> [C, D] = [nudge, wink]     # List assignment

# 于是,变量数值对调运算时 会简单一些 >>> nudge, wink = wink, nudge # Tuples: swaps values >>> [a, b, c] = (1, 2, 3) # Assign tuple of values to list of names >>> (a, b, c) = "ABC" # Assign string of characters to tuple >>> a, b, c = string[0], string[1], string[2:] # Index and slice

高级一点的,括号匹配。

for (a, b, c) in [(1, 2, 3), (4, 5, 6)]: ...       # Simple tuple assignment
for ((a, b), c) in [((1, 2), 3), ((4, 5), 6)]: ... # Nested tuple assignment

 

Level 3 - rest赋值

  • 方便个别变量赋值

不是指针,而是rest.

>>> a, *b, c = 'spam'
>>> a, b, c
('s', ['p', 'a'], 'm')

如果没有内容,则是认定为空列表 [ ].

# 末尾是否还有东西
>>> a, b, c, *d = seq
>>> print(a, b, c, d)
1 2 3 [4]

>>> a, b, c, d, *e = seq
>>> print(a, b, c, d, e)
1 2 3 4 []

# 中间是否还有东西
>>> a, b, *e, c, d = seq
>>> print(a, b, c, d, e)
1 2 3 4 []

 

  • 方便提取个别变量

循环遍历时貌似有一点点好处,such as "head var in a list".

>>> L = [1, 2, 3, 4]
>>> while L:
...    front, *L = L             # Get first, rest without slicing
... front, L = L[0], L[1:] # 上一行的另一种写法:小变化,效果一样
...   print(front, L)
...
1 [2, 3, 4]
2 [3, 4]
3 [4]
4 []

 

Level 4 - 用 “第一个非空值” 赋值

节省条件判断的变量赋值。

X = A or B or C or None
# assigns X to the first nonempty (that is, true) object among A, B, and C, or to None if all of them are empty.

X = A or default

 

 

 

 

Expressions


Scope

全局变量 - global

各有各的作用域。

X = 99 # Global (module) scope X
def func():   X = 88 # Local (function) scope X: a different variable

细节繁琐,但大体是继承了C/C++的那一套东西。

• The enclosing module is a global scope. Each module is a global scope—that
is, a namespace in which variables created (assigned) at the top level of the module
file live. Global variables become attributes of a module object to the outside world
after imports but can also be used as simple variables within the module file itself.
• The global scope spans a single file only. Don’t be fooled by the word “global”
here—names at the top level of a file are global to code within that single file only.
There is really no notion of a single, all-encompassing global file-based scope in
Python. Instead, names are partitioned into modules, and you must always import
a module explicitly if you want to be able to use the names its file defines. When
you hear “globalin Python, think “module.”
• Assigned names are local unless declared global or nonlocal. By default, all
the names assigned inside a function definition are put in the local scope (the
namespace associated with the function call). If you need to assign a name that
lives at the top level of the module enclosing the function, you can do so by 
declaring it in a global statement inside the function. If you need to assign a name
that lives in an enclosing def, as of Python 3.X you can do so by declaring it in a
nonlocal statement.
• All other names are enclosing function locals, globals, or built-ins. Names
not assigned a value in the function definition are assumed to be enclosing scope
locals, defined in a physically surrounding def statement; globals that live in the
enclosing module’s namespace; or built-ins in the predefined built-ins module
Python provides.
• Each call to a function creates a new local scope. Every time you call a function,
you create a new local scope—that is, a namespace in which the names created
inside that function will usually live. You can think of each def statement (and
lambda expression) as defining a new local scope, but the local scope actually 
corresponds to a function call. Because Python allows functions to call themselves to
loop—an advanced technique known as recursion and noted briefly in Chapter 9
when we explored comparisons—each active call receives its own copy of the
function’s local variables. Recursion is useful in functions we write as well, to process structures whose shapes can’t be predicted ahead of time; we’ll explore it more
fully in Chapter 19.
Rules 

 

"内部"使用"外层"的变量

X = 88 # Global X
def func():
  global X
  X = 99 # Global X: outside def
func() print(X) # Prints 99

 

使用"其他文件"的变量

目的:Program Design: Minimize Cross-File Changes

# first.py
X = 99                 # This code doesn't know about second.py

通过import使用其他文件(模块)的内容.

# second.py
import first
print(first.X) # OK: references a name in another file first.X = 88 # But changing it can be too subtle and implicit

虽然是不同文件,加载到内存中就都一样了;通过函数改变总比直接改变要好一些。

# first.py
X = 99
def setX(new):      # Accessor make external changes explit
  global X        # And can manage access in a single place
  X = new
# second.py import first first.setX(88)      # Call the function instead of changing directly

 

闭包 - factory methods

返回"一个产品"

也就是返回一个具有自己id的一类产品。【工程方法】

def maker(N):
  def action(X):            # Make and return action
    return X + N        # action retains N from enclosing scope
  return action

add_5 = maker(5) # 先设置"大函数"的参数
add_5(2) # 再设置"小函数"的参数

去掉"灵活"的参数,写成内部的固定的形式. 

def maker():
    n = 4
    def action(x):
        return x + n
    return action

f = maker()
f(2)

 

返回"一系列产品"

进一步,返回内部 id 不一样的一系列产品。【lambda指针数组】

def makeActions():
  acts = []
  for i in range(5):                # Use defaults instead
    acts.append(lambda x, i=i: i ** x)    # Remember current i
  return acts

>>> acts = makeActions() >>> acts[0](2) # 0 ** 2 0 >>> acts[1](2) # 1 ** 2 1 >>> acts[2](2) # 2 ** 2 4 >>> acts[4](2) # 4 ** 2 16

 

改变 “外层变量” - Non-local

Ref: python中global 和 nonlocal 的作用域

nonlocal 关键字 用来在函数或其他作用域中使用外层 (非全局) 变量。

由此,一系列产品间"产生了联系".

def make_counter(): 
    count = 0 
    def counter(): 
        nonlocal count    # 表明想使用了外层变量!
        count += 1 
        return count 
    return counter 
       

def make_counter_test(): 
  mc = make_counter() 
  print(mc())        # 每调用一次,函数内部变量 count改变一次
  print(mc())
  print(mc())
 

make_counter_test()

输出:

1
2
3

 

 

Argument

已传进来

  • 参数类型检查

对应type()参看变量类型.

数据类型检查可以用内置函数isinstance()实现。

def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x
  • 强复制拷贝 

def changer(a, b):
    b = b[:]   # 拷贝技巧

a = 2 b[0] = 'spam' # Changes our list copy only

默认是引用,那么就是自己内部 copy 就好了。

 

在传递中

Arbitrary Arguments:星号传参(基础)

Table 18-1. Function argument-matching forms

func(value)       Caller Normal argument: matched by position
func(name=value)  Caller Keyword argument: matched by name
func(*iterable)   Caller Pass all objects in iterable as individual positional arguments
func(**dict)      Caller Pass all key/value pairs in dict as individual keyword arguments

def func(name)           Function Normal argument: matches any passed value by position or name
def func(name=value)     Function Default argument value, if not passed in the call
def func(*name)          Function Matches and collects remaining positional arguments in a tuple
def func(**name)         Function Matches and collects remaining keyword arguments in a dictionary
def func(*other, name)   Function Arguments that must be passed by keyword only in calls (3.X)
def func(*, name=value)  Function Arguments that must be passed by keyword only in calls (3.X)
View Code

The *name form collects any extra unmatched positional arguments in a tuple, and the **name form collects extra keyword arguments in a dictionary.

In Python 3.X, any normal or defaulted argument names following a *name or a bare * are keyword-only arguments and must be passed by keyword in calls.

  • 一颗星,list

>>> def f(*args): print(args)
>>> f(1,2,3)
(1, 2, 3)

  • 两颗星,dict
>>> def f(**args): print(args)
>>> f()
{}
>>> f(a=1, b=2)
{'a': 1, 'b': 2}
  • 混合星
>>> def f(a, *pargs, **kargs): print(a, pargs, kargs)
>>> f(1, 2, 3, x=1, y=2)
1 (2, 3) {'y': 2, 'x': 1}

 

触发编译器自动判断(高阶)

加星号,让编译器去判断,*pargs,就可以在乱序的情况下自动调整。

def func(a, b, c, d): 
print(a, b, c, d) >>> func(*(1, 2), **{'d': 4, 'c': 3}) # Same as func(1, 2, d=4, c=3) 1 2 3 4
>>> func(1, *(2, 3), **{'d': 4}) # Same as func(1, 2, 3, d=4) 1 2 3 4
>>> func(1, c=3, *(2,), **{'d': 4}) # Same as func(1, 2, c=3, d=4)   <--值得注意下! 1 2 3 4
>>> func(1, *(2, 3), d=4) # Same as func(1, 2, 3, d=4) 1 2 3 4
>>> func(1, *(2,), c=3, **{'d':4}) # Same as func(1, 2, c=3, d=4) 1 2 3 4

Python 3.X Keyword-Only Arguments (难点),具体参见:591/1594 

  • 一颗星

必须让编译器知道*b代表的参数组的长度。也就是*b后的参数必须有明确赋值。

def kwonly(a, *b, c):
  print(a, b, c)
>>> kwonly(1, 2, c=3) 1 (2,) 3
>>> kwonly(a=1, c=3) 1 () 3
>>> kwonly(1, 2, 3) TypeError: kwonly() missing 1 required keyword-only argument: 'c' 

也可以使用默认参数,默认参数是个好东西。

def kwonly(a, *b='spam', c='ham'):
  print(a, b, c)
>>> kwonly(1) 1 spam ham
>>> kwonly(1, c=3) 1 spam 3
>>> kwonly(a=1) 1 spam ham
>>> kwonly(c=3, b=2, a=1) 1 2 3
>>> kwonly(1, 2)  # 搞不清楚,2是b的,还是c的 TypeError: kwonly() takes 1 positional argument but 2 were given 
  • 两颗星
>>> def kwonly(a, **pargs, b, c):
SyntaxError: invalid syntax
>>> def kwonly(a, **, b, c):
SyntaxError: invalid syntax
>>> def f(a, *b, **d, c=6): print(a, b, c, d) # Keyword-only before **!
SyntaxError: invalid syntax
>>> def f(a, *b, c=6, **d): print(a, b, c, d) # Collect args in header

>>> f(1, 2, 3, x=4, y=5) # Default used 1 (2, 3) 6 {'y': 5, 'x': 4}
>>> f(1, 2, 3, x=4, y=5, c=7) # Override default 1 (2, 3) 7 {'y': 5, 'x': 4}

混合使用时,

>>> def f(a, c=6, *b, **d): print(a, b, c, d) # c is not keyword-only here!
>>> f(1, 2, 3, x=4) 1 (3,) 2 {'x': 4}

 注意事项,

>>> def f(a, *b, c=6, **d): print(a, b, c, d) # KW-only between * and **

# 直接告诉了编译器星号对应的参数(推荐) >>> f(1, *(2, 3), **dict(x=4, y=5)) # Unpack args at call 1 (2, 3) 6 {'y': 5, 'x': 4}

# 不能放在最后
>>> f(1, *(2, 3), **dict(x=4, y=5), c=7) # Keywords before **args! SyntaxError: invalid syntax
>>> f(1, *(2, 3), c=7, **dict(x=4, y=5)) # Override default 1 (2, 3) 7 {'y': 5, 'x': 4}
>>> f(1, c=7, *(2, 3), **dict(x=4, y=5)) # After or before * 1 (2, 3) 7 {'y': 5, 'x': 4}
>>> f(1, *(2, 3), **dict(x=4, y=5, c=7)) # Keyword-only in ** 1 (2, 3) 7 {'y': 5, 'x': 4}

 

“函数指针”参数

  • 求极值

有点类似于模板,或者是函数指针之类。

def minmax(test, *args):
  res = args[0]
  for arg in args[1:]:
    if test(arg, res):
      res = arg
  return res
def lessthan(x, y): return x < y # See also: lambda, eval def grtrthan(x, y): return x > y
print(minmax(lessthan, 4, 2, 1, 5, 6, 3)) # Self-test code print(minmax(grtrthan, 4, 2, 1, 5, 6, 3))

如此,设计minmax这类的函数就省事些。将定义规则和遍历操作 ”相分离“。

 

 

Decorator

增强“无参”的原函数

Ref: Python 函数装饰器

Ref: 廖雪峰讲装饰器

Ref: 这是我见过最全面的Python装饰器详解!没有学不会这种说法!

Stage 1,使用函数指针,只能如下写法.

from functools import wraps
import time

# 这里写一个装饰器 fn_timer
def fn_timer(function): @wraps(function) def function_timer(*args, **kwargs):
#---------------------------------- start
= time.time() result = function(*args, **kwargs) end = time.time() print ("Total time: %s seconds" % (str(end-start)))
#----------------------------------
return result return function_timer
# @fn_timer
def print_hello(): print("hello") print_hello = fn_timer(print_hello)  # 多写这么一句 print_hello()  

Stage 2,为了少写这么一句,就使用@fn_timer。并且无需修改原代码。

 1 from functools import wraps
 2 import time
 3 
 4 # 这里写一个装饰器 fn_timer
 5 def fn_timer(function):
 6     @wraps(function)
 7     def function_timer(*args, **kwargs):
 8         #----------------------------------
 9         start = time.time()
10         result = function(*args, **kwargs)
11         end   = time.time()
12         print ("Total time: %s seconds" % (str(end-start)))
13         #----------------------------------
14         return result
15 
16     return function_timer
17 
18 @fn_timer
19 def print_hello():
20     print("hello")
21 
22 print_hello()
View Code

 

@wrapper 加不加?

决定了原函数是“原函数”?还是已经变成了“增强后的函数”。

也就是说,通过wraps告诉wrapper函数:“要彻底封装,不让使用者知道,还是留一点线索给他”

from functools import wraps

def test(func):
    def wrapper(*args,**kwargs):
        '''我是说明1'''
        print('args: ',  args)
        print('kwargs: ',kwargs)

        args = (11,22,33)
        kwargs['name'] = 'Test_C'

        return func(*args,**kwargs)

    return wrapper


@test
def fun_test(*args,**kwargs):
    '''我是说明2'''
    print('我是一个函数')
    print('---> ',args,kwargs)


fun_test(1,2,3,4,a=123,b=456,c=789)
print('*'*20)
print(fun_test.__name__)
print(fun_test.__doc__)
View Code

 

  • 应用场景
Authorization
Logging

 

装饰器装饰“有参”函数

  • 提出问题

上面的例子没有参数,如果有参数会如何?

因为只会去写 “一个装饰器”,所以 “参数个数不定” 也需要考虑进去。

 

  • 解决问题

利用 *args, **kwargs即可。

装饰的函数,如果有返回值,则一致;如果没有返回值,则返回 None。

# 万能装饰器,修饰各种参数
def celebrator(func):
  def inner(*args, **kwargs):
    print('我是新功能')
    ret = func(*args, **kwargs)
  return inner


@celebrator
def myprint(a):
  print(a)

myprint('python小白联盟')

 

装饰器本身带“参数”

不带参数,需要写两个类似的装饰器函数。

def printequal(func):
    def inner():
        print('=====')
        func()
    return inner

def printstar(func):
    def inner():
        print('*****')
        func()
    return inner

@printequal
@printstar
def myprint():
    print('python jiu cai.')

myprint()
View Code

带参数后,可将这两个函数合并。

def get_celebrator(char):
    def print_style(func):
        def inner():
            print(char*5)
            func()
        return inner
    return print_style

@get_celebrator(
'(') @get_celebrator(')') def myprint(): print('python jiu cai.') myprint()

   

End.

posted @ 2018-01-23 09:12  郝壹贰叁  阅读(326)  评论(0编辑  收藏  举报