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)
来源:oschina
链接:https://my.oschina.net/u/4271089/blog/3795944