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
"""

 

posted @ 2020-06-18 09:43  _yanghh  阅读(408)  评论(0编辑  收藏  举报