item系列
__getitem__(self, item) 对象通过 object[key] 触发 __setitem__(self, key, value) 对象通过 object[key] = value 触发 __delitem__(self, key) 对象通过 del object[key] 触发
class Func: def __getitem__(self, item): # object[item] 触发 return self.__dict__[item] def __setitem__(self, key, value): # object[key] = value 触发 self.__dict__[key] = value def __delitem__(self, key): # del object[key] 触发 print('delitem: 删除key') del self.__dict__[key] def __delattr__(self, item): # del object.item 触发 print('delattr: 删除key') del self.__dict__[item] f = Func() f['name'] = 'hkey' # __setitem__ f['age'] = 20 # __setitem__ print(f.name) # 对象属性原本的调用方式 print(f['name']) # __getitem__ del f['name'] # __delitem__ print('------') del f.age # __delattr__
要注意反射的 __delattr__ 和 __delitem__ 使用不同的方式触发不同的特殊方法。
class Fib: def __getitem__(self, item): if isinstance(item, int): a, b = 1, 1 for x in range(item): a, b = b, a+b return a if isinstance(item, slice): start = item.start stop = item.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a+b return L f = Fib() print(f[9]) print(f[:10]) 执行结果: 55 [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
__str__ 和 __repr__
当直接打印一个对象的时候,显示的是一段内存地址。
In [1]: class Person: ...: def __init__(self, name): ...: self.name = name In [2]: p = Person('hkey') In [3]: p Out[3]: <__main__.Person at 0x2be9f5c9128> In [4]: print(p) <__main__.Person object at 0x000002BE9F5C9128>
为了便于更好的理解,我们定义一个__str__方法
In [1]: class Person: ...: def __init__(self, name): ...: self.name = name ...: def __str__(self): ...: return "hello %s." % self.name In [2]: p = Person('hkey') In [3]: p Out[3]: <__main__.Person at 0x2858bd468d0> In [4]: print(p) hello hkey.
定义了__str__方法,直接输出对象还是打印的内存地址,并没有走__str__方法中定义的格式,用print输出信息却调用了__str__方法定义的内容
In [1]: class Person: ...: def __init__(self, name): ...: self.name = name ...: def __repr__(self): ...: return "hello %s." % self.name In [2]: p = Person('hkey') In [3]: p Out[3]: hello hkey. In [4]: print(p) hello hkey.
定义了__repr__方法,不管是直接打印对象还是通过print打印对象,都是走的__repr__中定义的格式。
总结:
__repr__ 和 __str__ 这两个方法都是用于显示的,__str__是面向用户的,而__repr__是面向程序员
使用print打印操作会首先尝试__str__和str内置函数,它通常应该返回一个友好的提示
当__str__不存在的时候,会去找__repr__是否定义,定义则打印__repr__中定义的内容
当我们想在所有环境下都统一显示的话,可以添加__repr__方法。
当我们想在不同的环境下支持不同的显示,就可以定义__repr__方法和__str__方法,终端交互使用__repr__,用户使用__str__方法
__new__
__new__ 是在新式类中出现的方法,它作用在构造函数之前,可以这么理解,在python中存在于类里面的构造方法__init__()负责将类实例化,而在__init__() 启动之前,__new__()决定是否要使用该__init__()方法,因为__new__()可以调用其他类的构造方法或者直接返回别的对象作为本类的实例。
具体参考:https://www.cnblogs.com/ifantastic/p/3175735.html
要记住的是:在实例化时,__new__() 先与 __init__() 方法执行.
通常来讲,新式类开始实例化时,__new__()方法会返回cls(cls代指当前类)的实例,然后该类的__init__()方法作为构造方法会接收这个实例(self)作为自己的一个参数,然后依次传入__new__()方法中接收的位置参数和命名参数。
In [1]: class Foo: ...: def __init__(self, *args, **kwargs): ...: print('in init function.') ...: def __new__(cls, *args, **kwargs): # 实例化时,首先执行__new__方法 ...: print('in new function.') ...: return object.__new__(cls, *args, **kwargs) In [2]: f = Foo() in new function. in init function.
对于__new__ 和 __init__ 的个人理解如下:
首先,我们把一个类比一个生物,使用__new__方法是创建这个生物的本体(实例),当本体(实例)创建好,才能使用__init__来创建这个本体的属性
一个单例模式来印证上面的描述:
In [1]: class Foo: ...: def __init__(self, *args, **kwargs): ...: pass ...: def __new__(cls, *args, **kwargs): ...: if not hasattr(cls, '_instance'): ...: cls._instance = object.__new__(cls, *args, **kwargs) ...: return cls._instance In [2]: one = Foo() In [3]: two = Foo() In [4]: id(one) Out[4]: 2173030026152 In [5]: id(two) Out[5]: 2173030026152 In [6]: print(one == two) True In [7]: print(one is two) True
通过__new__方法,首先创建实例的本体,当第二次实例化时,通过if判断,这个本体已经存在,就直接返回,然后在调用__init__()方法附加属性值
python3 正常的使用格式:
class Foo(object): def __new__(cls, *args, **kwargs): return object.__new__(cls) def __init__(self, name): self.name = name f = Foo('hkey') print(f.name)
__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无需定义,因为python是一门高级语言,程序员在使用时无需关心内存和释放,因为此工作都是交给python解释器来执行的,所有,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
在 pycharm中当程序执行完,自动触发 __del__ 方法
class Foo: def __init__(self, name): self.name = name def __del__(self): print('执行__del__.') f = Foo('hkey') # 执行结果: 执行__del__. __del__ 在类中适合做一些关闭文件句柄等操作。
__call__
当对象+()时候[object()] 触发
class Foo: def __init__(self, name): self.name = name def __call__(self, *args, **kwargs): print('执行__call__.') f = Foo('hkey') f() # 执行结果: 执行__call__.
__len__
使用 len(object) 触发
class Foo: def __init__(self, name): self.name = name def __len__(self): return 10 # 注意返回结果必须是 int 类型 f = Foo('hkey') print(len(f)) # 执行结果: 10
__hash__
调用 hash(object) 触发
class Foo: def __init__(self): self.a = 1 self.b = 5 def __hash__(self): return hash(str(self.a) + str(self.b)) f = Foo() print(hash(f)) # 执行结果: 2068706206124340336
__eq__
当两个对象进行比较时,触发
class Foo: def __init__(self): self.a = 1 self.b = 5 def __eq__(self, other): if self.a == other.a and self.b == other.b: return True return False a = Foo() b = Foo() print(a == b) # 执行这个比较的时候,就调用了 __eq__ 方法 # 执行结果: True
实例练习:纸牌
from collections import namedtuple Card = namedtuple('Card', ['rank', 'suit']) class FranchDeck: ranks = [x for x in range(2, 11)] + list('JQKA') suits = ['红桃', '黑桃', '方块', '梅花'] def __init__(self): '''创建一副牌''' self._card = [Card(rank, suit) for rank in FranchDeck.ranks for suit in FranchDeck.suits] def __len__(self): '''统计牌数''' return len(self._card) def __getitem__(self, item): '''通过object[index]取牌''' return self._card[item] def __setitem__(self, key, value): '''调用 shuffle 的时候需要有__setitem__方法''' self._card[key] = value f = FranchDeck() print(f[:4]) # 按照顺序取牌 from random import shuffle shuffle(f) # 随机排列 print(f[:4]) # 切片随机取4张 # 执行结果: [Card(rank=2, suit='红桃'), Card(rank=2, suit='黑桃'), Card(rank=2, suit='方块'), Card(rank=2, suit='梅花')] [Card(rank=7, suit='红桃'), Card(rank=3, suit='方块'), Card(rank='J', suit='红桃'), Card(rank=5, suit='黑桃')]