Loading... ## Python类中的魔术方法 ### 特殊属性 ``` __name__ 对象名称 __module__ 类定义所在的模块名 __class__ 对象或类所属的类 __bases__ 类的基类元组,顺序为它们在基类列表中出现的顺序 __doc__ 函数, 类 文档; 如果没有定义默认为 None __mro__ 类的mro, class.mro()返回的结果保存在__mro__中 __dict__ 类或实例的属性, 可写的字典 ``` ### 特殊方法 ``` __dir__ 是通过调用内建函数dir()获取到对象的属性, 方法 ``` ### 魔术方法 ``` 1. 创建与销毁 __init__ __del__ 2. hash 3. bool 4. 可视化 5. 运算符重载 __eq__ __lt__ __gt__ 6. 容器和大小 7. 可调用对象 8. 上下文管理 with相关的 9. 反射 10. 描述器 难点 11. 其他杂项 ``` ## 一. 可哈希 hash > 关于hash, 只能哈希一些不可变的对象, 比如1, 2,(1,2,3,4). 可变类型的数据都是不可hash的, 比如 list ``` # hash(obj) <==> obj.__hash__() class Test: def __hash__(self): # __hash__需要返回一个整数 return 1 # 实现不可hash类型 __hash__ = None # 为None就是不可hash类型 # 同时引出一个set去重的机制, set去重是比对的对象__qe__方法 # 所以set不是根据hash去重的, 是先使用is比对两个对象是否为同一个内存,如果是就只直接去重, 否者判断两个对象是否 == # hash还会出现hash冲突 ``` ### 判断一个对象是否是可hash的 ``` from collections import Hashable num = 1 isinstance(num, Hashable) issubclass(int, Hashable) ``` ## 二. 可布尔的 > 关于`__bool__`, 如果类没有定义`__bool__`方法, 就找`__len__`方法, 费0为真, 如果`__len__`方法也没有定义, 那么所有实例都返回为真 ``` class Test: def __bool__(self): return True def __len__(self): return 0 # bool(A()) 是先找__bool__方法, 如果没有定义就去找__len__, __len__没有定义就返回True # 为什么集合类型的数据为空的时候bool(OBJ)为False, 其实就是类中没有定义__bool__方法, 但是有__len__方法, 为空的时候__len__ => 0 所以返回的是False ``` ## 三. 可视化 ``` __str__ # 求对象的字符串输出, str(OBJ),会print(OBJ),format(OBJ) __repr__ # 对象的表现形式, 内部调用的 __bytes__ # bytes(OBJ)相当于类型强制转换 ``` ``` class A: def __str__(self): # 这里必须返回字符串 return 'abc' def __repr__(self): # 这里必须返回字符串 return 'repr' def __bytes__(self): # 这里必须返回bytes类型的数据 return b'bytes' m = A() print(a) # 调用了 __str__ # 输出为: abc l = [A(), A()] # 调用了 __repr__ print(l) # 输出为: [repr, repr] bytes(m) # 调用了 __bytes__ 相当于进行了强制类型转换, 至于转换 结果需要编程者进行控制 ``` ## 四. 运算符重载 ``` # 比较运算符 __lt__ < __le__ <= __eq__ == __gt__ > __ge__ >= __ne__ != # 算数运算符 __add__ + __sub__ - __mul__ * __truediv__ / __mod__ % __floordiv__ // __pow__ ** __divmod__ divmod # 语法糖 __iadd__ += __isub__ -= __imul__ *= __itruediv__ /= __imod__ %= __ifloordiv__ //= __ipow__ **= ``` ``` # e.g class Point: def __init__(self, x, y): self.x = x self.y = y def __hash__(self): return hash((self.x, self.y)) def __eq__(self, other): return self.x == other.x and self.y == other.y def __add__(self, other): return self.__class__(self.x + other.x, self.y + other.y) def __sub__(self, other): return self.__class__(self.x - other.x, self.y - other.y) def __str__(self): return '<Point ({}, {})>'.format(self.x, self.y) def __repr__(self): return self.__repr__() if __name__ == '__main__': a = Point(1, 2) c = Point(1, 9) print(a) print(c) print(a + c) print(a - c) ``` ## 五. 容器方法 ``` __len__ 长度 len()会调用该方法 __iter__ 迭代容器时调用, 返回一个新的迭代器对象 __contains__ in成员运算符, 没有实现就调用__iter__方法遍历 __getitem__ 实现self[key]访问.序列对象, key接受整数为索引,或者切片.对于set和dict, key为hashable. key不存在引发KeyError __setitem__ 同上,不过该方法是进行设置值的 __missing__ 字典使用__getitem__()调用时, key不存在执行该方法 ``` ## 六.可调用对象 ``` # __call__ class Test: def __init__(self, name): self._name = name def _show_name(self): return self._name def __call__(self, *args, **kwargs): print(args) print(kwargs) print(self._show_name()) a = Test() a() # a是一个可调用对象 ``` ## 七.上下文管理 ``` """ 重要的几个方法 __enter__ 进入执行的方法, 会将enter的返回值绑定到as后面的变量上 __exit__ 退出执行的方法 场景 with OBJECT as ALIAS: pass """ class Point(object): def __init__(self): print('init') def __enter__(self): """ with 进入执行的方法 :return: """ print('__enter__') return self def __exit__(self, exc_type, exc_val, exc_tb): """ with 退出执行的方法 :param exc_type: :param exc_val: :param exc_tb: :return: """ print('__exit__') with Point() as point: pass """ with 异常处理能力 __exit__的三个参数 exc_type 异常类型 exc_val 异常的值 exc_tb taceback 没有异常的话这三个值为None 拦截异常, 增加返回值return True """ with Point() as point: # raise Exception('Error') sys.exit() # 如上, 就算遇到异常, exit方法一定会执行 # 错误照常抛出, __exit__还是会正常执行 print('point', point) ``` `contextlib.contextmanager`他是一个装饰器实现的上下文管理, 装饰一个函数, 而不用像类一样实现`__enter__`和`__exit__`方法 ``` import contextlib # 使用例子 @contextlib.contextmanager def foo(): print('enter') # 相当于__enter__ yield VALUE # 相当于enter return 的对象 print('exit') # 相当于__exit__ with foo() as f: print(f) ``` `functools.total_ordering` 与比较相关的装饰器 ``` from functools import total_ordering @total_ordering class A: def __init__(self, x): self.x = x def __eq__(self, other): return self.x == other.x def __lt__(self, other): return self.x < other.x if __name__ == '__main__': print(A(1) > A(2)) print(A(2) >= A(2)) print(A(98) <= A(33)) print(A(98) == A(33)) # 总结他的作用, 当类要重载比较运算符的时候, 只想实现==, > 但是又想拥有<=, >=, !=这些比较运算符, # total_ordering 可以简化操作, 必须定义一个__eq__, 比较__lt__ 需要定义一个 # 缺点: 效率不高, 对于需要频繁比较的场景不建议使用该装饰器 ``` ## 八.反射 > 反射, reflection,指的是运行时获取类型定义信息 ### 反射相关的函数和方法 ``` getattr(OBJ, name, [DEFAULT]) # 获取对象属性 setattr(OBJ, name, value) # 设置对象属性 hasattr(OBJ, name) # 判断对象是否有某属性 ``` ### 反射相关的魔术方法 ``` # __getattr__ 获取属性 # __setattr__ 设置属性 # __delattr__ 删除属性 # 该魔术方法对应getattr, setattr # 本质上就是调用实例的__getattr__或__setattr__ class Point: def __init__(self): self.x = 1 self.y = 2 def __setattr__(self, key, value): print('设置了属性') super(Point, self).__setattr__(key, value) def __getattr__(self, item): """在字典中没有才会到达这里""" print('获取了属性') return super(Point, self).__getattr__(item) def __delattr__(self, item): print('实例删除') def __getattribute__(self, item): """在找属性之前做的操作,比__getattr__先运行""" pass """ 实例的所有的属性访问, 第一个都会调用__getattribute__方法, 它阻止了属性的查找, 该方法应该返回(计算后的)值或抛出一个AttributeError异常 它的return值将作为属性查找的结果.如果抛出AttributeError异常, 则会直接调用__getattr__方法, 因为表示属性没有找到 """ ``` > 属性的查找顺序:`__getattribute__` -> `instace.__dict__` -> `instance.__class__.__dict__` -> `继承的祖先类(一直找到object)的__dict__` -> `调用__getattr__()` ## 九.描述器(比较绕) > 定义: 当一个类用到了这三个魔术方法(`__set__, __get__, __delete__`), 且该类的实例被用作另一个的属性, 就叫描述器 ``` # 例子 class A: def __init__(self): self.a = 'My is Class(A)' def __get__(self, instance, owner): """ :param instance: 使用者实例 :param owner: 属主类(实例的__class__) :return: """ # __get__方法只有当前实例是某个类的属性, 通过某个类访问该类的属性才能触发 print(self, instance, owner) return self class B: x = A() # 描述器 def __init__(self): self.x = A() self.y = 100 if __name__ == '__main__': b = B() print(A.a) # 不会调用A.__get__ print(B.x.a) # 会调用A.__get__ print(b.x.a) # 不会调用A.__get__ 因为类的属性搜索顺序, 他获取到的是实例的属性, 不是类的属性 ``` 如果仅实现了`__get__`, 就是 **非数据描述符 non-data descriptor** 同时实现了`__get__ + __set__或__delete__`, 就是 **数据描述符 data descriptor** 如果一个类的类属性设置为描述器, 那么它被称为owner属主 ``` # 当为一个数据描述器的时候 class A: def __init__(self): self.x = 'Is A' def __get__(self, instance, owner): print(self, instance, owner) return self def __set__(self, instance, value): print(instance. value) class B: m = A() def __init__(self): self.m = 100 # 因为是数据描述器, 这里会调用类A的__set__方法 self.m = A() # 和上面一样 if __name__ == '__main__': print(B.m.x) # 会触发A.__get__方法 b = B() # 根据上面的定义会触发两次A.__set__ print(b.m.x) # 一旦类的属性是一个描述器, 那么该属性优先级高于当前类的属性 # 官方或书籍中的定义: # 实例的__dict__优先于非数据描述器 # 数据描述器优于实例的__dict__ ``` * `staticmethod`和`classmethod`就是非数据描述器 * `property`是数据描述器 * 类中的所有方法都是非数据描述器,包括魔术方法 **以下实现了两个重要的类方法装饰器** ``` from functools import partial class StaticMethod(object): """静态方法""" def __init__(self, fn): self.fn = fn def __get__(self, instance, owner): return self.fn class ClassMethod(object): """类方法""" def __init__(self, fn): self.fn = fn def __get__(self, instance, owner): # 引入了偏函数 # 偏函数的作用是为函数提供默认的参数 return partial(self.fn, owner) class MyClass: @StaticMethod def add(x, y): return x + y @ClassMethod def show(cls, value): print(cls) print(value) if __name__ == '__main__': print(MyClass.__dict__) print(MyClass.add(50, 50)) my = MyClass() print(my.add(50, 50)) MyClass.show('HelloWorld') my.show('HelloWorld') ``` 顺带补一下偏函数 ``` from functools import partial # partial(函数对象, 参数1,参数2,...) # partial主要作用是包装函数对象,为函数提供一些默认参数, 并返回一个函数对象 # 如下 def add(x,y): return x + y # 此时我们想吧x固定下来, 固定为2 new = partial(add, 2) # 直接调用new print(new(2)) # -> 4 print(new(5)) # -> 7 ``` 通过描述器实现参数数据类型的强制判断 ``` import inspect from functools import wraps class Typed: """类型描述器""" def __init__(self, type): self.type = type self.value = None def __get__(self, instance, owner): return self.value def __set__(self, instance, value): if isinstance(value, self.type): self.value = value else: raise TypeError('需要传入一个类型{}的数据'.format(self.type)) class TypeAssert: """类型断言,装饰器; 其实给类添加数据描述器""" def __init__(self, cls): self.cls = cls wraps(cls)(self) def __call__(self, *args, **kwargs): params = inspect.signature(self.cls).parameters for name, param in params.items(): if param.annotiation != param.empty: # 类型不为空 setattr(self.cls, name, Typed(param.annotiation)) return self.cls(*args, **kwargs) @TypeAssert class Ponit: def __init__(self, x:int, y:int): self.x = x self.y = y p = Point(1,2) p.x = 'str' # 将会抛出错误 ``` 最后修改:2021 年 06 月 04 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏