python面向对象魔术方法

特殊属性

属性 含义
__name_ 类、函数、方法等的名字
__module_ 类定义所在的模块名
__class_ 对象或者类所属的类
__bases__ 类的基类的元组,顺序为它们在基类列表中出现的顺序
__doc__ 类、函数的文档字符串,如果没有定义则为None
__mro__ 类的mro,class.mro()返回的结果保存在__mro__
__dict__ 类的实例的属性,可写的字典
查看属性
方法 意义
__dir__ 返回类或者对象的所有成员名称列表。dir()函数就是调用__dir__()。
使用实例调用时,如果提供__dir__(),则返回其返回值,要求是可迭代对象。 如果没有提供__dir__(),则会从实例和类及祖先类中收集信息(尽可能多的)
如果dir([obj])参数obj包含方法__dir__(),该方法将被调用。如果参数obj不包含__dir__(),该方法将最大限
度地收集属性信息。
dir(obj)对于不同类型的对象obj具有不同的行为:
  1. 如果对象是模块对象,返回的列表包含模块的属性名和变量名。
  2. 如果对象是类或者类对象,返回的列表包含类的属性名,及它的基类的属性名。
  3. 如果obj不写,返回列表包含内容不同
    • 在模块中,返回模块的属性和变量名
    • 在函数中,返回本地作用域的变量名
    • 在方法中,返回本地作用域的变量名
魔术方法 分类:
  • 创建、初始化和销毁
    • __new____init____del__
  • hash
  • bool
  • 可视化
  • 运算符重载
  • 容器和大小
  • 可调用对象
  • 上下文管理
  • 反射
  • 描述器
  • 其他
实例化
方法 意义
__new__ 实例化一个对象
该方法需要返回一个值(本类型的实例),如果该值不是cls的实例,则不会调用__init__
该方法永远都是静态
【python面向对象魔术方法】__new__方法很少使用,即使创建了该方法,也会使用return super().__new__(cls)得到实例化对象,或者基类object的__new__方法来创建实例并返回。
hash
方法 意义
__hash__ 内建函数hash()调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash。
__eq__ 对应==操作符,判断2个对象是否相等,返回bool值
class A: def __init__(self, name, age=18): self.name = name def __hash__(self): return 1 def __eq__(self, other): return self.name == other.name def __repr__(self): return self.name print(hash(A('tom'))) print((A('tom'), A('tom'))) print([A('tom'), A('tom')]) print({('tom',), ('tom',)})

hash(x) ,x都一样,求得的hash应该是不变的,这是幂等性的缘故,一般来说,x不一样,hash应该不一样。
不同的hash算法,不同的x求得同样的hash值,这就是hash冲突。
__hash__方法只是返回一个hash值作为set的key,但是去重,还需要__eq__(等效==,就是内容相等)来判断2个对象是否相等,is判断的是内存地址,is相等的话就肯定是同一个元素。
hash值相等,只是hash冲突,不能说明两个对象是相等的。
因此,一般来说提供__hash__方法是为了作为set或者dict的key,所以去重要同时提供__eq__方法。
不可hash对象isinstance(p1, collections.Hashable)一定为False。
去重需要提供__eq__方法。
list类实例为什么不可hash
源码中有一句__hash__ = None,也就是如果调用__hash__()相当于None(),一定报错。
所有类都继承object,而这个类是具有__hash__()方法的,如果一个类不能被hash,就把__hash__设置为
None。
bool
方法 意义
__bool__ 内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。 定义__bool__(),这个函数的返回值必须要是bool类型。没有定义__bool__(),就找__len__()返回长度,非0为真。 如果__len__()也没有定义,那么所有实例都返回真。
两个对象(类和实例)可以当作True理解。
等效Fals的本质是:对于四大皆空:空串,空元组。空列表,空字典,先找bool,如果没有bool就看长度,当长度为0就恒为假。
可视化
方法 意义
__repr__ 内建函数repr()对一个对象获取字符串表达。
调用__repr__方法返回字符串表达,如果__repr__也没有定义,就直接返回object的定义,就是显示内存地址信息。
__str__ str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。如果没有定义,就去调用__repr__方法返回字符串表达,如果__repr__没有定义,就直接返回对象的内存地址信息。
__bytes__ bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象
class A: def __init__(self, name, age=18): self.name = name self.age = age def __repr__(self): return 'repr: {},{}'.format(self.name, self.age) def __str__(self): return 'str:{},{}'.format(self.name, self.age) def __bytes__(self): #return "{} is {}".format(self.name, self.age).encode() import json return json.dumps(self.__dict__).encode() print(A('tom')) # print函数使用__str__ print([A('tom')]) # []使用__str__,但其内部使用__repr__ print([str(A('tom'))]) # []使用__str__,其中的元素使用str()函数也调用__str__ print(bytes(A('tom')))

总结:一般首先找的是repr,当repr没有就找str,都没有就去object中去寻找。bytes方法转成二进制只是一种序列化的表达,和序列化还是有差别的,序列化是一种通用的二进制格式或者通用的中间格式,序列化是一种交互,转化成文本传输的,而这里的转换只是一种表达,给人展示用的。
注意不能通过判断是否带引号来判断输出值的类型,类型判断要使用type或isinstance
运算符重载
operator模块提供了以下特殊方法,可以将类的实例使用下面的操作符来操作
运算符 特殊方法 含义
<, <=, ==, >, >=, != __lt__, __le__, __eq__, __gt__, __ge__, __ne__ 比较运算符
+, -, *, /, %, //,**, divmod __add__, __sub__, __mul__, __truediv__, __mod__, __floordiv__, __pow__, __divmod__ 算数运算符
+=, -=, *=, /=, %=, //=, **= __iadd__, __isub__, __imul__, __itruediv__, __imod__, __ifloordiv__,__ipow__
@functools.total_ordering 装饰器 __lt__, __le__, __eq__, __gt__, __ge__是比较大小必须实现的方法,但是全部写完太麻烦,使用
@functools.total_ordering 装饰器就可以大大简化代码。
但是要求__eq__必须实现,其它方法__lt__, __le__, __gt__, __ge__ 实现其一。

但是:
__eq__等于可以推断不等于
__gt__大于可以推断小于
__ge__大于等于可以推断小于等于
也就是用3个方法,就可以把所有比较解决了,所以total_ordering可以不使用
容器相关方法
方法 意义
__len__ 内建函数len(),返回对象的长度(>=0的整数),如果把对象当做容器类型看,就如同list或者dict。bool()函数调用的时候,如果没有__bool__()方法,则会看__len__()方法是否存在,存在返回非0为真。
__iter__ 迭代容器时,调用,返回一个新的迭代器对象
__contains__ in 成员运算符,没有实现,就调用__iter__方法遍历
__getitem__ 实现self[key]访问。序列对象,key接受整数为索引,或者切片。对于set和dict,key为hashable。key不存在引发KeyError异常
__setitem__ __getitem__的访问类似,是设置值的方法
__missing__ 字典或其子类使用__getitem__()调用时,key不存在执行该方法
class A(dict): def __missing__(self, key): print('Missing key : ', key) return 0 a = A() print(a['k'])

可调用对象
def foo(): print(foo.__module__, foo.__name__) foo() # 等价于 foo.__call__()

函数即对象,对象foo加上(),就是调用此函数对象的__call__()方法。
可调用对象
方法 意义
__call__ 类中定义一个方法,实例就可以像函数一样调用
class Point: def __init__(self, x, y): self.x = x self.y = y def __call__(self, *args, **kwargs): return "".format(self.x, self.y) p = Point(4, 5) print(p) print(p()) class Adder: def __call__(self, *args): ret = 0 for x in args: ret += x self.ret = ret return ret adder = Adder() print(adder(4, 5, 6)) print(adder.ret)

练习:
定义一个斐波那契数列的类,方便调用,计算第n项
class Fib: def __init__(self): self.items = [0, 1, 1] def __call__(self, index): if index < 0: raise IndexError('Wrong Index') if index < len(self.items): return self.items[index] for i in range(3, index+1): self.items.append(self.items[i-1] + self.items[i-2]) return self.items[index] print(Fib()(100))

上例中,增加迭代的方法、返回容器长度、支持索引的方法
class Fib: def __init__(self): self.items = [0, 1, 1]def __call__(self, index): return self[index]def __iter__(self): return iter(self.items)def __len__(self): return len(self.items)def __getitem__(self, index): if index < 0: raise IndexError("Wrong Index") if index < len(self.items): return self.items[index]for i in range(len(self), index + 1): self.items.append(self.items[i - 1] + self.items[i - 2]) return self.items[index]def __str__(self): return str(self.items)__repr__ = __str__fib = Fib() print(fib(5), len(fib))# 全部计算 print(fib(10), len(fib))# 部分计算 print("------") for x in fib: print(x, end=" ")print() print(fib[5], fib[9])# 索引访问,不计算

上下文管理对象
当一个对象同时实现了__enter__()和__exit__()方法,它就属于上下文管理的对象。
方法 意义
__enter__ 进入与此对象相关的上下文。如果存在该方法,with语法会把该方法的返回值作为绑定到as子句中指定的变量上
__exit__ 退出与此对象相关的上下文。
class Point: def __init__(self): print("init")def __enter__(self): print("enter")def __exit__(self, exc_type, exc_val, exc_tb): print("exit")with Point() as f: print("-" * 30) raise Exception('error')print("=====end=======")

实例化对象的时候,并不会调用enter,进入with语句块调用__enter__方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法。
with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。
注意,with并不开启一个新的作用域。
上下文管理很安全,不管是碰到异常都还是会正常执行
__enter__方法返回值就是上下文中使用的对象,with语法会把它的返回值赋给as子句的变量。
方法的参数 __enter__方法 没有其他参数。
__exit__方法有3个参数:
__exit__(self, exc_type, exc_value, traceback)
这三个参数都与异常有关。
如果该上下文退出时没有异常,这3个参数都为None。
如果有异常,参数意义如下
exc_type,异常类型
exc_value,异常的值
traceback,异常的追踪信息
__exit__方法返回一个等效True的值,则压制异常;否则,继续抛出异常
上下文应用场景
  1. 增强功能
    在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。
  2. 资源管理
    打开了资源需要关闭,例如文件对象、网络连接、数据库连接等
  3. 权限验证
    在执行代码之前,做权限的验证,在enter中处理
contextlib.contextmanager contextlib.contextmanager
它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter__和__exit__方法。
对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
也就是这个装饰器接收一个生成器对象作为参数。
import contextlib@contextlib.contextmanager def sub(x, y):# 为生成器函数增加了上下文管理 print("enter") start = datetime.datetime.now() try: yield x - y# yield的值只能有一个,作为__enter__方法的返回值 finally: detla = (datetime.datetime.now() - start).total_seconds() print(detla) print("exit")with sub(6, 2) as f: time.sleep(2) print("------------") print(f) print("~~~~~~~~~~")#输出 enter ------------ 4 ~~~~~~~~~~ 2.000654 exit

当yield发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。
  • 把yield之前的当做_enter_方法执行
  • 把yield之后的当做_exit_方法执行
  • 把yield的值作为_enter_的返回值
总结 :
如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的方式加__enter__和__exit__方法方便。

    推荐阅读