[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
一些值得注意的用法.
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 “global” in 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.
"内部"使用"外层"的变量
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)
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()
@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__)
- 应用场景
装饰器装饰“有参”函数
- 提出问题
上面的例子没有参数,如果有参数会如何?
因为只会去写 “一个装饰器”,所以 “参数个数不定” 也需要考虑进去。
- 解决问题
利用 *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()
带参数后,可将这两个函数合并。
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.