1. __getitem__、__setitem__、__iter__、__next__魔法方法
__index__方法是对象被作为索引访问时调用的魔法方法,那么当对象要进行索引访问时,调用什么魔法方法呢?答案是__getitem__魔法方法。
class C:def __getitem__(self, index):print(index)
当索引位切片时,打印的是一个内置函数slice,也就是说,slice函数相当于一个切片:
使用[slice(2, 6)]进行索引访问,等同于[2:6],左闭右开。类似的,s[7:]相当于s[slice(7, None)],s[::4]相当于s[slice(None, None, 4)]。
根据索引或者切片进行赋值的操作会调用__setitem__魔法方法:
class D:def __init__(self, data):self.data = datadef __getitem__(self, index):return self.data[index]def __setitem__(self, index, value):self.data[index] = value
如果通过for循环获取数据,那么也会调用__getitem__方法:
class D:def __init__(self, data):self.data = datadef __getitem__(self, index):return self.data[index] * 2
d = D([1, 2, 3, 4, 5])
for i in d:print(i, end=' ')
上述例子中for循环最终会调用到__getitem__魔法方法,其实并不是一定的,前提是对象没有__iter__魔法方法和__next__魔法方法。在Python中,如果定义了__iter__魔法方法,则认为是可迭代对象,如果一个可迭代对象定义了__next__魔法方法,则认为是一个迭代器。比如列表,是一个可迭代对象,但因为没有__next__方法,所以不是迭代器。for循环所做的事,就是将对象传给内置函数iter()获取一个迭代器,然后再去调用迭代器的__next__方法实现遍历。例如以下for循环:
x = [1, 2, 3, 4, 5]
for i in x:print(i, end=' ')
等价于:
_ = iter(x)
while True:try:i = _.__next__() except StopIteration:breakprint(i, end=' ')
自定义一个迭代器,根据传入的起始位置和终止位置,计算区间内的数乘以2:
class Double:def __init__(self, start, stop)self.value = start - 1self.stop = stopdef __iter__(self):return selfdef __next__(self):if self.value == self.stop:raise StopIterationself.value += 1return self.value * 2
2. __contains__、__bool__、__len__魔法方法
使用运算符in或者not in就会触发__contains__魔法方法:
class C:def __init__(self, data):self.data = datadef __contains__(self, item):print("嗨~")return item in self.data
如果没有__contains__魔法方法,使用了in或者not in运算符,那么Python就会去触发__iter__和__next__魔法方法:
class C:def __init__(self, data):self.data = datadef __iter__(self):print("Iter", end='->')self.i = 0return selfdef __next__(self):if self.i == len(self.data):raise StopIterationprint("Next", end='->')item = self.data[self.i]self.i += 1return item
使用bool函数会触发__bool__魔法方法:
class D:def __bool__(self):print("Bool")return True
如果没有实现__bool__魔法方法,则会去调用__len__魔法方法:
class D:def __init__(self, data):self.data = datadef __len__(self):print("Len")return len(self.data)
如果不想要某个魔法方法不生效,直接赋值为None,比如__contains__赋值为None,使用in或者not in运算符就会报错,而不是去调用__iter__和__next__魔法方法:
class C:def __init__(self, data):self.data = datadef __iter__(self):print("Iter", end='->')self.i = 0return selfdef __next__(self):if self.i == len(self.data):raise StopIterationprint("Next", end='->')item = self.data[self.i]self.i += 1return itemdef __contains__ = None
3. __str__、__repr__魔法方法
__str__魔法方法和__repr__魔法方法分别对应str()和repr()这两个函数,虽然在大多数情况下他们得到的结果是一样的,但是本质上str的结果是给人看的,repr的结果是给程序看的:
可以看到对于字符串,repr函数的结果会包一层引号,为什么会包一层引号,就是为了给eval函数解析用的。将str函数的结果传给eval函数,会报错,因为程序会去寻找FishC这个变量,因为没找到所以报错,而repr的结果传给eval函数,程序会认为'FishC'是一个字符串,从而正确识别。
所以eval函数被认为是repr的反函数,即传一个参数给repr函数,然后再将结果传给eval函数,最终会得到参数本身:
如果没有定义__str__魔法方法,调用str函数时就会去触发__repr__魔法方法:
class C:def __repr__(self):return 'I love FishC'
但如果实现了__str__魔法方法,但是没有实现__repr__魔法方法,调用repr函数时,是不会触发__str__魔法方法的:
class C:def __str__(self):return 'I love FishC'
如果只定义了__str__魔法方法,没有定义__repr__魔法方法,打印对象列表,是不会触发到__str__魔法方法的,打印的结果是对象的地址:
但是反过来,如果只定义了__repr__魔法方法,没有定义__str__魔法方法,打印对象列表,则可以触发__repr__魔法方法:
使用__str__方法和__repr__方法实现对象在不同场景下的不同显示效果:
class C:def __init__(self, data):self.data = datadef __str__(self):return f'data = {self.data}'def __repr__(self):return f'C({self.data})'def __add__(self, other):self.data += other
4. property函数和装饰器
使用property函数,可以将变量由另一个变量全权代理:
class C:def __init__(self):self._x = 250def getx(self):return self._xdef setx(self, x):self._x = xdef delx(self):del self._xx = property(getx, setx, delx)
property函数的参数是函数,所以property经常被当做装饰器来使用,如果一个get函数使用property函数来修饰,则可以将函数名等价于变量名来使用:
class E:def __init__(self):self._x = 250@propertydef x(self):return self._x
这样也就实现了只读的对象属性。为了使对象可写可删,还需实现对象名.setter和deleter方法:
class E:def __init__(self):self._x = 250@propertydef x(self):return self._x@x.setterdef x(self, value):self._x = value@x.deleterdef x(self):del self._x
5. 描述符以及用描述符实现property函数
描述符定义:如果实现了__get__、__set__、__delete__这三个方法其中任意一个或者多个,那么这个类就可以称为描述符。描述符可不是管理本类对象的属性,是用来管理其他类的属性,类似于property函数:
class D:def __get__(self, instance, owner):print(f"get~\nself -> {self}\ninstance -> {instance}\nowner -> {owner}")def __set__(self, instance, value):print(f"set~\nself -> {self}\ninstance -> {instance}\nvalue -> {value}")def __delete__(self, instance):print(f"delete~\nself -> {self}\ninstance -> {instance}")class C:x = D()
可以看到,self参数对应的是描述符类的对象,instance参数对应的是被描述符所拦截的属性所在的类的对象,owne参数对应的是描述符所拦截的属性所在的类。使用描述符实现上述property函数的代码"x = property(getx, setx, delx)":
class D:def __get__(self, instance, owner):return instance._xdef __set__(self, instance, value):instance._x = valuedef __delete__(self, instance):del instance._xclass C:def __init(self, x=250):self._x = xx = D()
使用描述符实现property函数(这里定义位MyProperty,功能与property一样):
class MyProperty:def __init__(self, fget=None, fset=None, fdel=None):self.fget = fgetself.fset = fsetself.fdel = fdeldef __get__(self, instance, owner):return self.fget(instance)def __set__(self, instance, value):self.fset(instance, value)def __delete__(self, instance):self.fdel(instance)class C:def __init(self, x=250):self._x = xdef getx(self):return self._xdef setx(self, value):self._x = valuedef delx(self):del self._xx = MyProperty(getx, setx, delx)
实现装饰器功能:
class MyProperty:def __init__(self, fget=None, fset=None, fdel=None):self.fget = fgetself.fset = fsetself.fdel = fdeldef __get__(self, instance, owner):return self.fget(instance)def __set__(self, instance, value):self.fset(instance, value)def __delete__(self, instance):self.fdel(instance)def getter(self, func):self.fget = funcreturn selfdef setter(self, func):self.fset = funcreturn selfdef deleter(self, func):self.fdel = funcreturn selfclass D:def __init(self, x=250):self._x = x@MyPropertydef x(self):return self._x@x.setterdef x(self, value):self._x = value@x.deleterdef x(self):del self._x