*的用法–解包和收集多余参数
# 平行赋值
a, *b, c = [1, 2, 3, 4]
# a =1, b=[2, 3] , c=4
# a, *b, c = *[1, 2, 3, 4] #错误的用法,右侧不能使用*
def f0(*a):
print(a)
f0(1, 2, 3)
# (1, 2, 3)
def f1(a, b, c):
print(a, b, c)
f1(*(1, 2, 3))
# 1, 2, 3
* 一个作用是收集多余的元素。平行赋值中,等号右侧默认解包,所以使用 * 号是多余的,会报错。
但是在参数传递中,参数是默认不解包的,因此可以用* 强制要求解包。
可迭代对象都支持解包
怎么定义可迭代对象,从鸭子类型来说,实现了 __iter__的类是可迭代的。该方法需要能够返回一个迭代器,用于迭代时元素的产生。因此迭代器负责生成值具体执行者, 迭代器除了实现 __iter__ 之外,还要实现__next__ 方法, 这两者分别对应于Python的内置函数,iter()构建生成器和next()产生一个值。
可以使用isinstance和issubclass检查类型
from collections.abc import Iterator, Iterable
class Myclass:
def __next__(self):
pass
def __iter__(self):
pass
myinstance = Myclass()
print(isinstance(myinstance, Iterator))
print(issubclass(Myclass, Iterator))
运行可以发现,Myclass并没有显示继承Iterator, Iterable,但是也能通过上述检查, 这似乎不合理。这是由于Iterator, Iterable实现了__subclasshook__。该方法是基于属性检查的,也就是说只要自定义类或者其父类实现了迭代器的两个方法就可以被认定为是其子类。
除了上述特点外,自定义的序列类型,即使没有实现上述两个特殊方法,也是可以解包和迭代的。
当解释器需要可迭代对象时,会默认调用iter(x)获得可迭代对象,该方法执行如下操作:
1、检查对象x是否实现了 __iter__, 如果有就调用它返回一个可迭代对象。
2、如果没有, 尝试调用__getitem__从序列头部开始创建一个可迭代对象。因此实现了__getitem__的类也是可以用来解包和迭代的。但是该类和其对象是通不过可迭代对象或者迭代器检查的, 因为没有实现 __iter__ ,__next__ 。
3、如果上述两步都失败了,就返回一个TypeError
参考:
Fluent Python chapter14, Chapter2