Python 类特殊方法
object 是一个基类,或称之为元类。在 python2.x 上,不继承 object 类的称之为经典类,继承了 object 类的称之为新式类。
在 python3 种默认都是新式类,也即是所有的自定义类、基类都会继承object类。
object 类里面内置了许多特殊方法,这些方法的开头和结尾都是双下划线。
1. __dir__:返回一个列表,其中包含所有的属性和方法名(包含特殊方法)。函数原型如下:
def __dir__(self, *args, **kwargs): """ Default dir() implementation. """ pass
下面举一个例子:
class Test(): pass print(Test().__dir__()) # 也可执行 dir(obj) 来获取所有属性和方法 """ 输出如下: ['__module__', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__'] """
2. __setattr__:当我们执行obj.属性名=属性值或setattr(obj,属性名,属性值),即为属性赋值时被调用。
class Foo(object): def __init__(self): self.name = 'Alex' # 打印 call __setattr__ # 重写__setattr__,为了使调用时打印输出,obj.xxx = value时调用 def __setattr__(self, key, value): print('call __setattr__') return object.__setattr__(self, key, value) # 调用基类的方法来赋值 f = Foo() f.name = 'Jone' # 打印 call __setattr__
3. __getattribute__和__getattr__:使用类的对象进行obj.属性名或getattr(obj,属性名)来取对象属性的值的时候被调用,不管属性是否存在,
__getattribute__方法都会被调用。如果属性存在,则返回该属性的值,如果属性不存在,则返回None。我们在使用hasattr(obj,属性名)来判断
某个属性是否存在时,__getattribute__方法也会被调用。
与__getattr__的区别:
__getattribute__方法不管属性是否存在,都会被调用。而__getattr__只在属性不存在时调用,默认会抛出:
'AttributeError: xxx object has no attribute yyy'这样的错误,但我们可以对其进行重写,做我们需要的操作。
class Test(object): def __init__(self): self.name = 'Alex' def __getattribute__(self, item): print("call __getattribute__") return object.__getattribute__(self, item) def __getattr__(self, item): print('call __getattr__') print("%s不存在,但我可以返回一个值" % item) return 54 f = Test() print(f.name) # name 属性存在,打印 call __getattribute__ print(f.age) # age 属性不存在,打印 call __getattribute__,会返回None, # 然后打印 call __getattr__, 但 __getattr__ 方法返回了54(如果没有被重写,则直接报异常),所以这里打印54。
4. __str__和__repr__:默认打印的是对象的内存地址等信息。
1)只重写__str__,则只能用于定义print(obj)时打印的内容,交互式命令行输出obj还是函数地址。
>>> class Foo(object): ... def __str__(self): ... return "我是Foo" ... >>> f1 = Foo() >>> print(f1) 我是Foo >>> f1 <__main__.Foo object at 0x0000023BF701C550>
2)只重写__repr__,则用于同时定义python命令行输出obj的内容,以及print(obj)的打印内容。
>>> class Foo(object): ... def __repr__(self): ... return "我是Foo" ... >>> f1 = Foo() >>> print(f1) 我是Foo >>> f1 我是Foo
3)如果__str__和__repr__都被重写了,则__str__负责print的信息,而__repr__负责命令行直接输出obj的信息。
>>> class Foo(): ... def __str__(self): ... return "我是Foo---str" ... def __repr__(self): ... return "我是Foo---repr" ... >>> f1 = Foo() >>> print(f1) 我是Foo---str >>> f1 我是Foo---repr
5. __new__:__new__方法是一个静态方法,在调用时,传入你需要实例化的类名以及初始化参数列表,创建一个cls类的对象,并返回。其函数原型如下:
@staticmethod def __new__(cls, *more): """ Create and return a new object. See help(type) for accurate signature. """ pass
注意以下几点:
1)__new__在object被指定为@staticmethod,但更像是一个@classmethod,第一个参数传入类本身cls。
2)__new__在__init__之前运行,为传入的类(Foo)生成一个实例并返回。
3)__init__在__new__之后执行,为__new__返回的类实例进行初始化。
4)__init__是一个实例方法,是由实例来调用的。所以要执行__init__方法,必须先要由__new__生产一个实例。这就是为什么先执行__new__方法的原因。
class Foo(object): # 后于__new__方法执行,为__new__方法生成的对象进行初始化 def __init__(self, name, age): # __new__返回的对象作为self传入__init__ print("执行__init__方法") self.name = name self.age = age # __new__方法先于__init__方法执行,用于生成一个指定类的对象 def __new__(cls, *args, **kwargs): # 接收参数cls为Foo类,然后是f1 = Foo("Alex",age=32)里的name和age print("执行__new__方法", args, kwargs) ret = object.__new__(cls) # 调用__new__生成一个Foo对象 print(ret) # 打印 <__main__.Foo object at 0x000001AD868F8668> return ret # 返回生成的 Foo 对象 f1 = Foo("Alex",age=32) """ output: 执行__new__方法 ('Alex',) {'age': 32} <__main__.Foo object at 0x000001B6523A64F0> 执行__init__方法 """
6. __gt__、__lt__、__ge__、__le__:这几个都是用于比较大小的,我们可以对其进行重写,来自定义对象如何比较大小。它们的原型如下:
# 判断是否大于等于 greater than or equal,在obj >= other时调用 def __ge__(self, *args, **kwargs): """ Return self >= value. """ pass # 判断是否大于 greater than,在obj > other时调用 def __gt__(self, *args, **kwargs): """ Return self > value. """ pass # 判断是否小于等于 less than or equal,在obj <= other时调用 def __le__(self, *args, **kwargs): """ Return self <= value. """ pass # 判断是否小于 less than,在obj<other时调用 def __lt__(self, *args, **kwargs): """ Return self < value. """ pass
下面举一个简单的例子:
class Car(): def __init__(self, price): self.price = price def __lt__(self, other): print("execute __lt__") return self.price < other.price c1 = Car(100) c2 = Car(200) if c1 < c2: print("car1 is cheaper.") else: print("car2 is cheaper.")
7. __eq__和__hash__:__hash__
实际上是返回一个int值(计算类中所有成员的hash值),用来唯一标记这个对象。户自定义类中,如果你没有实
现__eq__
和__hash__
函数,那么class会继承到默认的__eq__
和__hash__
函数。
先来解释下什么是可hash对象:如果一个对象是可哈希的,那么在它的生存期内必须不可变(需要一个哈希函数__hash__),而且可以和其他对象比
较(需要比较方法__eq__).相同的对象一定有相同的哈希值。
1)不可hash:list, set, dict
2)可hash:数值,字符串,boolean
3)自定义类对象可不可hash:当类中只含可hash的成员时,该类定义的对象是可hash的。当我们实现的类想成为不可hash的类,则可以重写__eq__方法,
然后不重写__hash__,__hash__方法会被置None,该类的对象就不可hash了。如果不重写__eq__,则会使用基类的实现,比较对象时会比较它们的hash值。
class Foo(object): def __init__(self): self.name = 'Alex' def __eq__(self, other): return self.name == other.name f1 = Foo() f2 = Foo() print(f1 == f2) # True print(f1 is f2) # False print(hash(f1) == hash(f2)) # 抛出异常TypeError错误,因为此时__hash__=None
8. __dict__属性:在 Python 类的内部,无论是类属性还是实例属性,都是以字典的形式进行存储的,其中属性名作为键,而值作为该键对应的值。
为了方便用户查看类中包含哪些属性,Python 类提供了 __dict__ 属性。该属性可以用类名或者类的实例对象来调用。
1)每个类有自己的__dict__属性,就算存着继承关系,父类的__dict__ 并不会影响子类的__dict__,两者不共用。子类有自己的__dict__, 父类也有自
己的__dict__,子类的类属性、静态方法、普通方法、内置属性等放在子类的dict中,父类的放在父类dict中。
2)实例对象也有自己的__dict__属性, 存储 self.xxx 信息(只有属性,不包含方法),与1)不同的是父子类对象公用__dict__。
3)内置的数据类型没有__dict__属性。
我们在查找一个类属性或者对象属性的时候,也是通过__dict__字典来查的,查找顺序如下:
1)首先查找实例 obj.__dict__。
2)如果1)中未找到,则去查找类 type(obj).__dict__。
3)如果2)也未找到,则去查找父类的 __dict__,查找顺序也是先实例后类。
class Test(object): x = 1 # 类属性 def __init__(self, y): self.y = y # 实例属性 def fun(self): print(self.y) t = Test(2) print(t.__dict__) """ 通过实例调用__dict__,输出如下: {'y': 2} """ print(Test.__dict__) """ 因为__dict__不共用,所以输出内容不会涉及到基类object { '__module__': '__main__', 'x': 1, '__init__': <function Test.__init__ at 0x0000021E999C83A0>, 'fun': <function Test.fun at 0x0000021E999C8430>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None } """ print(vars(t)) # __dict__ 也可以用 vars 函t数替代,功能完全相同 t.fun() # 在 type(t).__dict__ 中寻找,找到了直接返回 print(t.y) # 在 t.__dict__ 中寻找,找到了 y 直接返回 print(t.__dict__['y']) # 上面的调用机制实际上是这样的 print(t.x) # 在t.__dict__中找不到x,于是到type(t).__dict__中找到了x,并返回其值 print(type(t).__dict__['x']) # 上面的调用机制实际上是这样的
9. __getitem__:这个方法在 object 类里面没有被定义,因为一旦定义了这个方法,那么所有对该类对象的索引运算都会被拦截。
凡是在类中定义了这个 __getitem__ 方法,那么它的实例对象(假定为 obj),可以像这样 obj[index] 取值,当实例对象做
obj[index] 操作时,会调用类中的方法__getitem__ 方法。
class DataBase: def __init__(self, id, address): self.id = id self.address = address self.d = { self.id: 1, self.address: "192.168.1.1" } def __getitem__(self, key): return self.d.get(key, "default") data = DataBase(1, "192.168.2.11") print(data["hi"]) print(data[data.id]) """ default 1 """
10. __len__:如果一个类表现得像一个 list,要获得有多少个元素,就得用 len();要让 len() 函数正常工作,类必须提供一个特
殊的方法__len__(),才能返回元素的个数。
class Fib: def __init__(self, num): a, b, L = 0, 1, [] for i in range(num): L.append(a) a, b = b, a+ b self.numbers = L def __str__(self): return str(self.numbers) def __len__(self): return len(self.numbers) f = Fib(10) print(f) print(len(f)) """ [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 10 """