Python 魔术方法
Python解释器碰到特殊句法时,会使用魔术方法去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,以两个下划线结尾
- 举例:obj[key]背后就是__getitem__方法
- 没有实现__getitem__方法,无法使用[]获取类中的dict
1 class A: 2 adict = dict(one=1,two=2) 3 4 a = A() 5 print(a['one']) 6 print(a['two'])
输出:
TypeError: 'A' object is not subscriptable
- 实现__getitem__方法后,可以使用[]
class A: adict = dict(one=1,two=2) def __getitem__(self, item): return A.adict[item] a = A() print(a['one']) print(a['two'])
输出:
1
2
- 一个纸牌类的例子,用来说明__len__和__getitem__的作用
- 实现了这两个方法,可以用于获得长度,用[]访问数据,甚至切片和循环
import collections ''' collections.namedtuple:用于构建一个只有属性没有方法的简单类 参数: 1、类名 2、一个字符串列表,表示各个属性 ''' Card = collections.namedtuple('Card', ['rank', 'suit']) class Deck(): # +号可以用于列表的连接 ranks = [str(i) for i in range(2,11)] + list('JQKA') suits = ['spades', 'hearts', 'clubs', 'diamonds'] def __init__(self):
# 双重for循环产生笛卡尔积 self._cards = [Card(rank,suit) for suit in self.suits for rank in self.ranks ] def __len__(self): return len(self._cards) def __getitem__(self, position): return self._cards[position] deck = Deck(); # 调用__len__ print(len(deck)) # 调用__getitem__ print(deck[0]) # 切片,继续调用__getitem__ print(deck[:3]) print(deck[12::13]) print('-' * 100) # for循环,还是调用__getitem__ for d in deck: print(d) print('-' * 100) # 排序 suit_values = {'spades':3,'hearts':2,'clubs':1,'diamonds':0} def order(card): rank_value = Deck.ranks.index(card.rank) return rank_value*len(suit_values)+suit_values[card.suit] for d in sorted(deck,key=order): print(d)
输出:
52
Card(rank='2', suit='spades')
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
[Card(rank='A', suit='spades'), Card(rank='A', suit='hearts'), Card(rank='A', suit='clubs'), Card(rank='A', suit='diamonds')]
----------------------------------------------------------------------------------------------------
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
……
Card(rank='Q', suit='diamonds')
Card(rank='K', suit='diamonds')
Card(rank='A', suit='diamonds')
----------------------------------------------------------------------------------------------------
Card(rank='2', suit='diamonds')
Card(rank='2', suit='clubs')
Card(rank='2', suit='hearts')
Card(rank='2', suit='spades')
……
Card(rank='A', suit='diamonds')
Card(rank='A', suit='clubs')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')
- 一个向量类,用魔术方法__add__和__mul__重载加号和乘号
- 加号和乘号都是中缀运算符,基本原则是不改变操作对象,而是产出一个新的值
from math import hypot class Vector: def __init__(self,x,y): self.x=x self.y=y
# 在repr中被调用
# 如果没有实现__str__,print使用__repr__替代 def __repr__(self): return 'Vector({0},{1})'.format(self.x,self.y) def __abs__(self): return hypot(self.x,self.y)
# 重载加号需要实现这个方法
# 注意,重载这个方法之后,只能实现a*b,而不能实现b*a def __add__(self, other): if not isinstance(other,Vector): raise TypeError('only permits Vector!') return Vector(self.x+other.x,self.y+other.y)
# 默认情况下,bool(自定义类的实例)总是为true,除非自定义类实现__bool__方法
# 如果没有实现__bool__方法,bool(x)会尝试调用x.__len__(),若返回0,bool为False,否则为True def __bool__(self): return bool(abs(self))
# 实现向量与实数的乘法
# 同样,重载这个方法只能实现Vector*n,而不能实现n*Vector def __mul__(self, other): return Vector(self.x*other,self.y*other) v1 = Vector(3,4) print('v=',v1) print('-' * 100) print('abs(v)=',abs(v1)) print('-' * 100) v2 = Vector(4,5) print('Vector(3,4)+Vector(4,5)=',v1+v2) print('-' * 100) print('Vector(3,4)*3=',v1*3)
输出:
v= Vector(3,4)
----------------------------------------------------------------------------------------------------
abs(v)= 5.0
----------------------------------------------------------------------------------------------------
Vector(3,4)+Vector(4,5)= Vector(7,9)
----------------------------------------------------------------------------------------------------
Vector(3,4)*3= Vector(10.5,14.0)
- 利用__format__函数指定格式
- "{:格式说明}".format(obj) 或 format(obj, 格式说明)的调用中,python将格式说明传递给obj.__format__函数作为第二个参数
class Man: def __init__(self, name, age): self.name, self.age = name, age def __format__(self, format_spec): if format_spec == "": return str(self) result = format_spec.replace('%name', self.name).replace('%age', self.age) return result class People: def __init__(self, *people): self.people = list(people) def __format__(self, format_spec): if format_spec == "": return str(self) # 如果不直接使用格式说明,而是传递给另外的类,需要用到格式化字符串的嵌套 return ",".join('{0:{1}}'.format(c,format_spec) for c in self.people) # 这样调用效果相同 # return ",".join(format(c,format_spec) for c in self.people) alice = Man("Alice", "18") bob = Man("Bob", "19") candy = Man("Candy", "20") ''' 这里:后面的“%name is %age years old.”是格式说明 它匹配Man类__format__(self, format_spec)的第二个形参format_spec ''' print('{:%name is %age years old.}'.format(alice)) print('-' * 100) # 这样调用效果相同 print(format(alice,'%name is %age years old.')) print('-' * 100) # 嵌套使用格式说明 ppl = People(alice,bob ,candy) print("{:%name is %age years old}".format(ppl))
输出:
Alice is 18 years old.
----------------------------------------------------------------------------------------------------
Alice is 18 years old.
----------------------------------------------------------------------------------------------------
Alice is 18 years old,Bob is 19 years old,Candy is 20 years old
- 使用__eq__()和__hash__()对对象进行比较
class Complex: def __init__(self,real,i): self.real = real self.i=i a = Complex(1,2) b = Complex(1,2) # 在没有实现__eq__的情况下,==使用对象的id进行比较 # 而hash是利用对象id/16取整 print(a==b) print(hash(a)==hash(b)) 输出:
False
False
class HashedComplex(Complex): def __init__(self,real,i): super().__init__(real,i) # 在实现__eq__的同时,也要实现__hash__方法,让他们的结果保持一致 def __hash__(self): return self.real ^ self.i # 用对象属性的异或实现__eq__ def __eq__(self, other): if not isinstance(other,Complex): raise TypeError('cannot compared with '+other.__class__.__name__) return self.real==other.real and self.i == other.i a = HashedComplex(1,2) b = HashedComplex(1,2) # 实现了__eq__的情况下,==使用对象的__eq__进行比较 print(a==b) print(hash(a)==hash(b))
输出:
True
True
- 使用 __setitem__()、__getitem__()、__delitem__()和__contains__()实现集合模拟
class Room: def __init__(self,size): self.size=size def __setitem__(self, key, value): print('__setitem__('+str(key)+':'+str(value)+')') self.__dict__[key]=value def __getitem__(self, item): print('__getitem__('+str(item)+')') return self.__dict__[item] def __delitem__(self, key): print('__delitem__('+key+')') del self.__dict__[key] def __contains__(self, item): print('__contains__('+item+')') return self.__dict__.__contains__(item) room = Room(100) # 用obj.key设置属性不会调用__setitem__ room.size=150 # 设置不存在的属性也不会调用__setitem__ room.door=5 # 用obj.key取得属性不会调用__getitem__ print('room size=',room.size) print('room door=',room.door) print('-' * 20,'我是分割线','-' * 20) # 用[]设置属性会调用__setitem__ room['window'] = 6 # 用[]取得属性会调用__getitem__ print('room[window]=',room['window']) print('-' * 20,'我是分割线','-' * 20) # 调用__contains__判断对象是否有该属性 print('window' in room) # 调用__delitem__删除room的window属性 del room['window'] # 调用__contains__判断对象是否有该属性 print('window' in room)
输出:
room size= 150
room door= 5
-------------------- 我是分割线 --------------------
__setitem__(window:6)
__getitem__(window)
room[window]= 6
-------------------- 我是分割线 --------------------
__contains__(window)
True
__delitem__(window)
__contains__(window)
False
- 迭代枚举相关魔术函数:__iter__、__reversed__、__next__
- Iterable和Iterator
- 首先我觉得还是要先理清楚Iterable和Iterator的关系,我们先来看一下类的层次关系
class Iterator(Iterable) | Method resolution order: | Iterator | Iterable | builtins.object | | Methods defined here: | | __iter__(self) | | __next__(self) | Return the next item from the iterator. When exhausted, raise StopIteration class Iterable(builtins.object) | Methods defined here: | | __iter__(self)
- 可以看到Iterator继承了Iterable,它们都需要实现__iter__函数,Iterator多出了一个__next__
- 说说个人理解吧,Iterator是真正用于迭代的迭代器对象,所以它必须实现__next__,客户函数可以通过next()函数取得它的下一个值。它的__iter__函数通常返回自己,毕竟自己就是个迭代器么
- 而Iterable是一个自定义的随便什么类,其实和迭代器没什么关系。可能它正好有一些值,你如果想让客户按一定次序访问这些值,就实现__iter__方法,返回一个上面说的迭代器给客户用。那么我们把实现了__iter__函数,自己又不是迭代器的对象,称为可迭代对象,即Iterable
- __reversed__
- 这个函数也返回一个迭代器,即Iterator,但产生数据的方式是反过来的,所以如果想让客户用正反两个顺序访问一个Iterable里的数据,就实现两个Iterator,一个通过__iter__返回,一个通过__reversed__返回
谈了这么多,看一个具体例子吧
from collections.abc import Iterable,Iterator # WeekDay的正向Iterator class WeekDayiterator(): def __init__(self, idx, data): self.index = idx self.data = data def __iter__(self): return self def __next__(self): result = self.data[self.index] self.index = (self.index + 1) % len(self.data) return result # WeekDay的反向Iterator class WeekDayiterator_reversed(): def __init__(self, idx, data): self.index = idx self.data = data def __iter__(self): return self def __next__(self): result = self.data[self.index] self.index = (self.index - 1) % len(self.data) return result # 自定义的Iterable类 class WeekDay(): data = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'] def __init__(self,idx=0): self.index=idx def __iter__(self): return WeekDayiterator(self.index,self.data) def __reversed__(self): return WeekDayiterator_reversed(self.index,self.data) print('WeekDay is Iterable:',issubclass(WeekDay,Iterable)) print('WeekDay is Iterator:',issubclass(WeekDay,Iterator)) print('WeekDayiterator is Iterator:',issubclass(WeekDayiterator,Iterator)) print('WeekDayiterator_reversed is Iterator:',issubclass(WeekDayiterator_reversed,Iterator)) print('-'*100) wd1 = WeekDay() wd1_iter = iter(wd1) for i in range(10): print(next(wd1_iter)) print('-'*100) wd1_reversed = reversed(wd1) for i in range(10): print(next(wd1_reversed))
输出:
WeekDay is Iterable: True
WeekDay is Iterator: False
WeekDayiterator is Iterator: True
WeekDayiterator_reversed is Iterator: True
----------------------------------------------------------------------------------------------------
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
Monday
Tuesday
----------------------------------------------------------------------------------------------------
Sunday
Saturday
Friday
Thursday
Wednesday
Tuesday
Monday
Sunday
Saturday
Friday
- 可调用模拟:__call__
- 实现了__call__的类,实例出的对象是可调用对象,即可以在对象后面加(),像函数那样调用它
class Add_To_Box: box=[] def __call__(self, *args, **kwargs): self.box.extend(args) add_to_box = Add_To_Box() add_to_box('Cat','Dog') add_to_box('Pig') print(','.join(add_to_box.box))
输出:
Cat,Dog,Pig
- 上下文管理:__enter__和__exit__
- 实现这两个魔术方法,可以使用with语法,让Python自动在建立对象后和with语句全部结束前分别调用对象的这两个方法
class OpenBox: box = ['lots of gold','lots of shit'] password = 'show me money' input='' def __init__(self,pswd): print('Box is created!') self.input=pswd def __enter__(self): print('Box is opened!') if self.input == self.password: return self.box[0] else: return self.box[1] def __exit__(self, exc_type, exc_val, exc_tb): print('Box is closed!') with OpenBox('show me money') as box: print(box) print('-'*100) with OpenBox('wrong password') as box: print(box)
输出:
Box is created!
Box is opened!
lots of gold
Box is closed!
----------------------------------------------------------------------------------------------------
Box is created!
Box is opened!
lots of shit
Box is closed!
- 创建实例相关魔术方法:__new__和__init__
- __new__在__init__之前调用,__new__是一个类方法
- 构造方法返回的实例,是由__new__先产生,然后传递给__init__初始化的
- 在重写__new__的时候,如果想返回这个类的实例,需要调用object.__new__
- 一个单例模式的例子
class Singleton: def __new__(cls, *args, **kwargs): print('__new__({0}) is called'.format(cls)) if not hasattr(cls,'instance'): cls.instance = object.__new__(cls) return cls.instance def __init__(self): print('__init__({0}) is called'.format(self)) s1 = Singleton() s2 = Singleton() print(s1 == s2)
输出:
__new__(<class '__main__.Singleton'>) is called
__init__(<__main__.Singleton object at 0x0000024BD91242E8>) is called
__new__(<class '__main__.Singleton'>) is called
__init__(<__main__.Singleton object at 0x0000024BD91242E8>) is called
True
- 属性管理相关魔术方法:__getattr__、__setattr__、__getattribute__
- 用点号运算符访问对象继承树中不存在的属性时自动调用__getattr__
- 使用obj.key=value方式设置属性时自动调用__setattr__,注意在__setattr__中要设置属性,需要使用self.__dict__,否则将出现无限递归
- 用点号运算符访问对象属性时,调用__getattribute__
- 注意在__getattribute__中访问自己的属性,需要使用 super().__getattribute__(attr_name),否则将无限递归
- 有了__getattribute__就不会访问__getattr__
class AttrException(Exception): pass class Animal: leg = 4 # privates里面保存私有属性 privates=[] def __init__(self,name): # 这里也会调用__setattr__() self.name = name def __getattr__(self, item): print('__getattr__({0}) is called'.format(item)) # 私有属性不让访问 if item in self.privates: raise AttrException("Private attribute!") try: return self.__dict__[item] except KeyError: print('{0} is not existing'.format(item)) def __setattr__(self, key, value): print('__setattr__({0},{1}) is called'.format(key,value)) self.__dict__[key] = value class Dog(Animal): pass class Tiger(Animal): privates = ['bottom'] def __getattribute__(self, item): print('__getattribute__({0}) is called'.format(item)) if item in super().__getattribute__('privates'): raise AttrException("Private attribute!") return super().__getattribute__(item) dog = Dog('Puppy') # 对已有属性不调用 __getattr__ dog.name # 访问不存在的属性,调用__getattr__ dog.age print('-' * 100) tiger = Tiger('King') # 对已有属性仍然调用__getattribute__ tiger.name tiger.bottom = "Don't touch" try: # 调用__getattribute__,发现是私有属性,抛错 tiger.bottom except AttrException as exc: print(exc.__repr__())
输出:
__setattr__(name,Puppy) is called
__getattr__(age) is called
age is not existing
----------------------------------------------------------------------------------------------------
__setattr__(name,King) is called
__getattribute__(__dict__) is called
__getattribute__(name) is called
__setattr__(bottom,Don't touch) is called
__getattribute__(__dict__) is called
__getattribute__(bottom) is called
AttrException('Private attribute!')
- 属性描述符相关魔术方法:__get__、__set__
- 属性描述符是一个类,有点特殊的类,它至少实现了__get__方法,也可以实现__set__甚至__delete__方法
- 属性描述符一般只会在另外一个类中使用,被当做那个类的类属性,而且一定要是类属性哦,因为它必须存在于类的__dict__中。
- 如果它存在于实例的__dict__中,Python不会去自动调用__get__和__set__,于是就废了
- 只实现了__get__方法的属性描述符被称为Non-Data Descriptor,同时实现了__set__的属性描述符被称为Data Descriptor
- 同名属性的访问顺序:
1、Data Descriptor
2、实例属性
3、Non-Data Descriptor
- 也就是说,实例属性会覆盖Non-Data Descriptor,但不会覆盖Data Descriptor
class DataDescriptor: def __get__(self, instance, owner): print('__get__({0}, {1}, {2})'.format(self,instance,owner)) def __set__(self, instance, value): print('__set__({0}, {1}, {2})'.format(self,instance,value)) class NonDataDescriptor: def __get__(self, instance, owner): print('__get__({0}, {1}, {2})'.format(self,instance,owner)) class AttributeOrder: descriptor1 = DataDescriptor() descriptor2 = NonDataDescriptor() def __init__(self,d1,d2): self.descriptor1 = d1 self.descriptor2 = d2 ao = AttributeOrder('d1','d2') # DataDescriptor不会被实例属性覆盖 print(ao.descriptor1) # NonDataDescriptor会被实例属性覆盖 print(ao.descriptor2)