文章目录
文章目录
前言
N19:不要把函数返回的多个数值拆分到三个以上的变量中
1、详解
2、总结
N20:遇到意外情况时应该抛出异常,不要返回None
1、详解
2、总结
N22:用数量可变的位置参数,给函数设计清晰的参数列表
1、详解
2、总结
前言
提示:Effective Python第二版,作者是Brett Slatkin, Google首席软件工程师,立足于python3,主要讲解原理与常见用法。
第3章主要讲解函数在使用过程中的一些规范。
N19:不要把函数返回的多个数值拆分到三个以上的变量中
1、详解
1)unpacking 机制允许python返回一个以上的值,以元组的形式;
def my_func():
return 1, 2
first, second = my_func()
2)? 返回多个值时,可以利用(带星号的表达式)接受没有被普通变量捕获的值;
如:计算鳄鱼的长度与平均长度之比,并返回最长和最短的两条鳄鱼对应的比值。
def get_avg_ratio(numbers):
average = sum(numbers)/len(numbers)
scaled = [x/average for x in numbers]
scaled.sort(reverse=True)
return scaled
longest, *middle, shortest = get_avg_ratio(lengths)
3)返回值过多时,比如返回5个值,会造成两个问题:一是容易搞错顺序造成bug,二是调用函数并拆分返回值的那行代码会非常长,可能需要拆行。(最好不要这么写!!!)
如,返回鳄鱼长度的最小值、最大值,、平均值、中位值和样本总数量。
def get_stats(numbers):
minimum = min(numbers)
maximum = max(numbers)
count = len(numbers)
average = sum(numbers)/count
sorted_numbers = sorted(number)
middle = count // 2
if count % 2 == 0:
lower = sorted_number[middle - 1]
upper = sorted_number[middle]
midian = (lower + upper) / 2
else:
midian = sorted_number[middle]
return minimum, maximum, average, median, count
minimum, maximum, average, median, count = get_stats(lengths)
2、总结
- 函数可以把多个值合起来通过一个元组返回给调用者,可以利用unpacking 机制拆分;
- 对于函数返回的多个值,将普通变量没有捕获的返回值全部捕获到一个(带星号的变量里);
- 返回值拆分到四个或四个以上的变量很容易出错,最好不要!可以通过小类或namedtuple实例完成。
N20:遇到意外情况时应该抛出异常,不要返回None
1、详解
1)很多人喜欢返回None 来表示特殊情况,但存在一个问题:在条件表达式中,None与0、空字符串无法区分,容易出错。
比如:构造除法函数,除数为0会报错,返回None值;在后续对返回值进行使用的时候,按照情况1 的写法还不至于出错,但是情况2的写法,对于被除数为0,返回0的情况,也会输出Invalid inputs, 从而使得程序出现问题。针对该情况,有两种解决方案。
def carefule_divide(a,b):
try:
return a/b
except ZeroDivisionError:
return None
# 情况1
x,y = 1,0
result = carefule_divide(x,y)
if result is None:
print('Invalid inputs')
# 情况2
x,y = 0,5
result = carefule_divide(x,y) # shouldn't
if result is None:
print('Invalid inputs')
2)方案一,构造二元组把计算结果分为两部分,第一个元素表示操作是否成功,第二个元素表示实际的计算值。使用时,调用者首先需要判断运算是否成功,再决定如何处理运算结果。(该方案的问题是,可能有一部分调用者容易忽略元组的第一个部分,从而无法有效区分。)
def carefule_divide(a,b):
try:
return True, a/b
except ZeroDivisionError:
return False, None
success, result = carefule_divide(x,y)
if not success:
print('Invalid inputs')
# 偷懒的调用者
_, result = carefule_divide(x,y)
if not result:
print('Invalid inputs')
3)方案二,不采用None表示特例,向调用方抛出异常,让其自行选择。
def carefule_divide(a,b):
try:
return a/b
except ZeroDivisionError:
raise ValueError('Invalid inputs')
# 调用方的工作
x,y = 5,2
try:
result = carefule_divide(x,y)
except ValueError:
print('Invalid inputs')
else:
print(f'Result:result')
2、总结
- 用返回值None表示特殊情况,很容易出错。因为在条件表达式中,None跟空字符串、0无法区分,都相当于False;
- 用异常表示特殊的情况,而不返回None,从而让调用者对异常情况进行处理。
N22:用数量可变的位置参数,给函数设计清晰的参数列表
1、详解
1)参数数量固定的函数,函数调用的时候可能会很乱;
比如values 没有值,也必须传一个空列表进去。
def log(message, values):
if not values:
print(message)
else:
values_str = '.'.join(str(x) for x in values)
print(f'{message}:{values_str}')
#调用
log('My number are', [1,2])
log('Hi there', [])
#输出
My number are: 1, 2
Hi there
2)? 让函数接受可变数量的位置参数,即varargs或star args(*args),能够使得函数调用更丝滑;
如:将log函数的第二个参数改为,*args,调用者只需要提供不带星号的参数即可。
def log(message, *values):
if not values:
print(message)
else:
values_str = '.'.join(str(x) for x in values)
print(f'{message}:{values_str}')
#调用
log('My number are', [1,2])
log('Hi there')
#输出
My number are: 1, 2
Hi there
3)把已有序列中的元素当成参数,传给参数个数可变的函数,可以在传递序列时使用*操作符。
这种方式存在两个问题。一是程序先把这些参数转为一个元组,再把他们当成可选的位置参数传给参数,这中间需要使用yield函数,如果输入值过多,会造成程序崩溃;二是如果需要新增位置参数,那原来的调用操作需要全部更新,否则会报错或者导致结果异常。
def log(message, *values):
if not values:
print(message)
else:
values_str = '.'.join(str(x) for x in values)
print(f'{message}:{values_str}')
#调用
favourites = [7,33,99]
log('Favorite colors', *favourites)
#输出
Favorite colors: 7, 33, 99
2、总结
- def 定义函数时,*args可以让函数接收数量可变的位置参数;
- 调用函数时,在序列前面加*,把其中的元素当成位置参数传给*args;
- *操作符加在生成器前,传递参数时,程序可能会因为内存耗尽而崩溃;
- 在接受*args的函数添加新的位置参数,会造成原来的调用方式需要改更,还有可能造成难以排查的bug。
|