把返回的值拆分到四个或四个以上的变量是很容易出错的,所以最好不要那么写,而是应该通过小类或 namedtuple 实例完成。
不用 None 的原因:
没办法与 0 和空白字符串之类的值区分,这些值都相当于 False。
用异常表示特殊情况:
案例:
def divide(a: float, b: float) -> float:
"""Divide by a and b
Raises:
ValueError: when the inputs can not be divided
:param a:
:param b:
:return:
"""
try:
res = a / b
return res
except ZeroDivisionError as e:
raise ValueError('Invalid inputs')
在表达式中引用某个变量时,Python 解释器会按照下面的顺序,在各个作用域(scope)里面查找这个变量,以解析(resolve)这次引用:
为了防止函数中的局部变量污染外围变量,当 python 的当前作用于和外围作用域有重名变量时,Python 的当前作用域会单独维护自己的变量,如果需要将该变量指向外围作用域,则可以使用nolocal
,但是不能侵入模块级别的作用域(以防污染全局作用域)。
尽量不要使用nolocal
,因为它造成的副作用有时很难发现。尤其是在那种比较长的函数里,nonlocal 语句与其关联变量的赋值操作之间可能隔得很远。
替代方案:如果 nonlocal 的用法比较复杂,那最好是改用辅助类来封装状态
让函数接受数量可变的位置参数(positional argument),可以把函数设计得更加清晰(这些位置参数通常简称 varargs,或者叫作 star args,因为我们习惯用*args
指代)
作用:
可以给最后一个位置参数加前缀*,这样调用者就只需要提供不带星号的那些参数,然后可以不再指其他参数,也可以继续指定任意数量的位置参数。函数的主体代码不用改,只修改调用代码即可。
令函数接受数量可变的位置参数可能会导致的问题:
def gen():
"""迭代器"""
for i in range(10):
yield i
def func(*arg):
"""如果得到的是带*的迭代器,必须要执行完迭代器才行,如果迭代器返回的数据过多,可能导致内存溢出"""
print(arg)
g = gen()
func(*g)
在给这种*arg 函数添加参数时,应该使用只能通过关键字来指定的参数(keyword-only argument)
定义函数时,如果想让这个函数接受任意数量的关键字参数,那么可以在参数列表里写上万能形参**kwargs,它会把调用者传进来的参数收集合到一个字典里面稍后处理
关键字参数的好处:
可选的关键字参数总是应该通过参数名来传递,而不应按位置传递。
def func(a, b, c=1):
"""c就是可选参数"""
print(a, b, c)
func(a=1, b=2)
参数的默认值只会在系统加载这个模块的时候,计算一遍,而不会在每次执行时都重新计算。
可能出现的问题:
import random
def test1(b, a={}):
a[b] = random.randrange(10)
return a
def test2(b, a=None):
a[b] = random.randrange(10)
return a
# {'h': 6}
print(test1('h'))
# {'h': 6, 'e': 0}
print(test1('e'))
# {'h': 8}
print(test2('h', {}))
# {'e': 5}
print(test2('e', {}))
把参数的默认值写成 None 的意义:
与类型注解搭配使用:
对于参数比较复杂的函数,我们可以声明只能通过关键字指定的参数(keyword-only argument),这样的话,写出来的代码就能清楚地反映调用者的想法了。这种参数只能用关键字来指定,不能按位置传递:
def func(a, b, *, c=1, d=2):
"""c,d只能通过关键字指定"""
print(a, b, c, d)
# TypeError: func() takes 2 positional arguments but 4 were given
# func(1, 2, 3, 4)
# 1 2 3 4
func(1, 2, c=3, d=4)
用法:参数列表里的*符号把参数分成两组,左边是位置参数,右边是只能用关键字指定的参数。
Python3.8 新特性:只能按照位置传递参数,参数列表中的/
符号,表示它左边的那些参数只能按位置指定。
*
和/
之间的参数:这两个符号之间的参数,既可以按位置提供,又可以用关键字形式指定。
def func2(a, b, /, *, c=1, d=2):
"""a,b只能通过位置指定,c,d只能通过关键字指定"""
print(a, b, c, d)
# 1 2 3 4
func2(1, 2, c=3, d=4)
# TypeError: func2() got some positional-only arguments passed as keyword arguments: 'a, b'
func2(a=1, b=2, c=3, d=4)
场景:函数装饰器返回的是装饰后的新函数,而不是原始函数,即修改了函数的元数据,可能导致 help()、对象序列化器无法正常运转。
def func(func):
def wrapper(*args, **kwargs):
print('前置处理')
return func(*args, *kwargs)
return wrapper
@func
def func1():
print('hello')
# 前置处理
# hello
func1()
# .wrapper at 0x102fca290>
print(func1)
解决:改用 functools 内置模块之中的 wraps 辅助函数来实现。wraps 本身也是个修饰器,它可以帮助你编写自己的修饰器。把它运用到 wrapper 函数上面,它就会将重要的元数据(metadata)全都从内部函数复制到外部函数。
from functools import wraps
def func(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('前置处理')
return func(*args, *kwargs)
return wrapper
@func
def func1():
print('hello')
# 前置处理
# hello
func1()
#
print(func1)