目录:构造方法
类的表示
操作符比较操作符
数值操作符一元操作符
常见算数操作符
反射算数运算符
增强复制运算符
类型转换操作符
访问控制(高级)
自定义序列(高级)
反射(高级)
本文介绍剩余的高级部分。
访问控制
通常情况下,我们在访问类或者实例对象的时候,会牵扯到一些属性访问的魔法方法,主要包括:__getattr__(self, name)
当用户试图访问不存在的属性时调用,你可以通过这个魔法方法来定义类的行为。
class Frob():
def __getattr__(self, item):
return f'{item}属性不存在'
if __name__ == '__main__':
f = Frob()
print(f.nb)
# 输出
nb属性不存在__getattribute__(self, name)
__getattribute__ 在访问存在的属性时调用,(先调用该方法,查看是否存在该属性,若不存在,接着去调用__getattr__(self, name))
举例:
class Frob():
def __getattr__(self, item):
return f'{item}属性不存在'
def __getattribute__(self, item):
print(f'正在获取属性{item}')
# super()必须写,如果属性不存在,需要手动继承父类(object)的__getattribute__()方法
super().__getattribute__(item)
if __name__ == '__main__':
f = Frob()
print(f.nb)
# 输出
正在获取属性nb
nb属性不存在
说明:
1)查看属性是否存在是会先调用__getattribute__();
2)因为 f 没有 nb这个属性,所以又会调用__getattr__()__setattr__(self, name, value) 设置实例对象的一个新的属性时调用。注意:如果__setattr __()希望分配给实例属性,则不应简单地执行self.name = value 这将导致对其自身的递归调用。而是应将值插入实例属性的字典中,例如self .__ dict __ [name] = value。
class Frob():
def __setattr__(self, key, value):
# 使用self.key = value,导致无限递归
# 抛RecursionError: maximum recursion depth exceeded
# self.key = value
self.__dict__[key] = value.upper()
if __name__ == '__main__':
f = Frob()
f.bamf = 'bamf'
print(f.bamf)
# 输出
BAMF__delattr__(self, name) 删除一个实例对象的属性时调用。和 __setattr__ 一样,使用它时也需要多加小心,防止产生无限递归(在 __delattr__ 的实现中调用 del self.name 会导致无限递归)。
class Frob():
def __setattr__(self, key, value):
self.__dict__[key] = value.upper()
def __delattr__(self, item):
print(f'正在删除{item}属性')
if __name__ == '__main__':
f = Frob()
f.bamf = 'bamf'
print(f.bamf)
del f.bamf
# 输出
BAMF
正在删除bamf属性
自定义序列
有许多办法可以让你的Python类表现得像是内建序列类型(字典,元组,列表,字符串等)。这些魔法方式是目前为止我最喜欢的。它们给了你难以置信的控制能力,可以让你的类与一系列的全局函数完美结合。在了解激动人心的内容之前,首先你需要掌握一些预备知识。
预备知识
既然讲到创建自己的序列类型,就不得不说一说协议了。协议类似某些语言中的接口,里面包含的是一些必须实现的方法。在Python中,协议完全是非正式的,也不需要显式的声明,事实上,它们更像是一种参考标准。
为什么我们要讲协议?因为在Python中实现自定义容器类型需要用到一些协议。首先,不可变容器类型有如下协议:想实现一个不可变容器,你需要定义 __len__ 和 __getitem__ (后面会具体说明)。可变容器的协议除了上面提到的两个方法之外,还需要定义 __setitem__和 __delitem__。最后,如果你想让你的对象可以迭代,你需要定义 iter ,这个方法返回一个迭代器。迭代器必须遵守迭代器协议,需要定义 __iter__(返回它自己)和 next 方法。
容器背后的魔法方法__len__(self)
返回容器的长度,可变和不可变类型都需要实现。
__getitem__(self, key)
定义对容器中某一项使用 self[key] 的方式进行读取操作时的行为。这也是可变和不可变容器类型都需要实现的一个方法。它应该在键的类型错误式产生 TypeError 异常,同时在没有与键值相匹配的内容时产生 KeyError 异常。
__setitem__(self, key)
定义对容器中某一项使用 self[key] 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 KeyError 和 TypeError 异常。
__iter__(self, key)
它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 iter() 函数调用,以及在类似 for x in container: 的循环中被调用。迭代器是他们自己的对象,需要定义 __iter__ 方法并在其中返回自己。
__reversed__(self)
定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法,
__contains__(self, item)
__contains__定义了使用 in 和 not in 进行成员测试时类的行为。你可能好奇为什么这个方法不是序列协议的一部分,原因是,如果__contains__没有定义,Python就会迭代整个序列,如果找到了需要的一项就返回 True 。
__missing__(self ,key)
__missing__ 在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 d , “george” 不是字典中的一个键,当试图访问 d[“george’] 时就会调用 d.__missing__(“george”))。
例子
让我们来看一个实现了一些函数式结构的列表,可能在其他语言中这种结构更常见(例如Haskell):
class FunctionalList:
'''一个列表的封装类,实现了一些额外的函数式
方法,例如head, tail, init, last, drop和take。'''
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values
def __len__(self):
return len(self.values)
def __getitem__(self, key):
# 如果键的类型或值不合法,列表会返回异常
return self.values[key]
def __setitem__(self, key, value):
self.values[key] = value
def __delitem__(self, key):
del self.values[key]
def __iter__(self):
return iter(self.values)
def __reversed__(self):
return reversed(self.values)
def append(self, value):
self.values.append(value)
def head(self):
# 取得第一个元素
return self.values[0]
def tail(self):
# 取得除第一个元素外的所有元素
return self.valuse[1:]
def init(self):
# 取得除最后一个元素外的所有元素
return self.values[:-1]
def last(self):
# 取得最后一个元素
return self.values[-1]
def drop(self, n):
# 取得除前n个元素外的所有元素
return self.values[n:]
def take(self, n):
# 取得前n个元素
return self.values[:n]
就是这些,一个(微不足道的)有用的例子,向你展示了如何实现自己的序列。当然啦,自定义序列有更大的用处,而且绝大部分都在标准库中实现了(Python是自带电池的,记得吗?),像 Counter , OrderedDict 和 NamedTuple 。
反射
你可以通过定义魔法方法来控制用于反射的内建函数 isinstance 和 issubclass 的行为。下面是对应的魔法方法:__instancecheck__(self, instance)
检查一个实例是否是你定义的类的一个实例(例如 isinstance(instance, class) )。
__subclasscheck__(self, subclass)
检查一个类是否是你定义的类的子类(例如 issubclass(subclass, class) )。
这几个魔法方法的适用范围看起来有些窄,事实也正是如此。我不会在反射魔法方法上花费太多时间,因为相比其他魔法方法它们显得不是很重要。但是它们展示了在Python中进行面向对象编程(或者总体上使用Python进行编程)时很重要的一点:不管做什么事情,都会有一个简单方法,不管它常用不常用。
这些魔法方法可能看起来没那么有用,但是当你真正需要用到它们的时候,你会感到很幸运,因为它们还在那儿(也因为你阅读了这本指南!)
推荐阅读