深入理解Python面向对象-特殊成员(补)

来自:python爬虫实战之路,作者:星星o在线

上一次我们讲了python类中的特殊成员,还有几个比较重要的忘记了

__new__

__new__方法是一个类方法,尽管它没有被classmethod装饰器修饰

  • 至少需要一个参数cls, 代表要实例化的类,这个参数有python解释器自动传入

  • 必须要有返回值,返回实例化的对象,如果返回None,则__init__方法不会被调用

  • __init__方法的self参数就是__new__返回的实例
    我们在使用类名创建对象时,会先调用__new__方法,分配内存空间,然后将其返回的对象引用作为参数传递给__init__使用。

使用场景

单例模式

class A:
    instance = None
    def __init__(self):
        print("A")

    def __new__(cls, , *args, **kwargs):
        print("A.__new__: ", cls)
        if cls.instance is None:
            cls.instance = super().__new__(cls)
        return cls.instance

c = C()

#输出:
C.__new__:  <class '__main__.C'>
C

定义一个instance类变量,然后重载__new__方法,这样就能确定此类只创建一个实例,但是返回值是一个实例对象,所以__init__每次还是会被调用。__new__重载方法的返回值有两种方式:

  • super().__new__(cls)

  • object.__new__(cls)

这两种方式有什么区别呢?看一下例子

class A:
    a_inst = None
    def __init__(self):
        print("A")
        self.name = "111"

    def __new__(cls, *args, **kwargs):
        print("A.__new__: ", cls)
        if cls.a_inst is None:
            cls.a_inst = super().__new__(cls)
        return cls.a_inst

class B(A):
    b_inst = None
    def __init__(self):
        super().__init__()
        print("B")
        self.name1 = 222

    def __new__(cls, *args, **kwargs):
        print("B.__new__: ", cls)
        if cls.b_inst is None:
            cls.b_inst = super().__new__(cls)
        return cls.b_inst

b = B()
print(b.a_inst is b.b_inst)
print(b.name)
print(b.name1)

#输出:
B.__new__:  <class '__main__.B'>
A.__new__:
  <class '__main__.B'>
A
B
True
111
222

可以看到A和B的__new__、__init__都会被调用,而且类变量a_inst和b_inst是同一个,name和name1均被正常初始化。A的__new__函数中打印的cls也是B,所以这就是super的原因了。如果把__new__中的super()换成object呢?

B.__new__:  <class '__main__.B'>
A
B
False
111
222

只有B的__new__被执行了,__init__都被调用了,name和name1还是被初始化了,因为A的__new__没有被调用,所以a_inst变量没有初始化,还是None。知道了这种区别那么我们在使用当中就要根据场景去判断使用哪种方式,一般情况下super的方式其实就可以了。

__getattr__

class A:
    def __init__(self):
        self.name = "名称"

    def __getattr__(self, item):
        print("getattr: ", item)
        return "没有属性:%s" % item

a = A()
print(a.name)
print(a.age)

#输出:
名称
getattr:  age
没有属性:age

通过例子可以看到,name属性是存在的,在访问时没有调用__getattr__方法,但是age属性是不存在的,访问时调用了__getattr__方法。在多重继承时,可以在父类中定义此方法,这样就可以防止多继承时属性访问异常。

__getattribute__

class A:
    def __init__(self):
        self.name = "名称"

    def __getattribute__(self, item):
        print("getattribute: ", item)
        self.name = "新名称"
        return super().__getattribute__(item)

a = A()
print(a.name)

#输出:
getattribute:  name
新名称

这里可以看到,在访问正常属性时,会先调用__getattribute__,而且在此函数中重新设置name属性值后,访问到的属性也更改了,如果在此方法中固定返回一个值,那么所有的属性访问都是同样的值

给对象和类绑定方法

  • 给对象绑定方法

from types import MethodType
class A:
    pass

def func(self):
    print("func")

a = A()
a.func = MethodType(func, a)
a.func()  # 输出:func

这种方式绑定的方法,只在此对象中存在,其他对象是不存在的

  • 给类绑定方法

class A:
    pass

def test(self):
    print("test")

a = A()
A.test = test
a.test()   # 输出:test

这种绑定方式会给类增加一个方法,在此类所定义的所有对象中都可以调用,即使是在绑定方法之前所定义的对象也可以。注意:以上两种绑定方法只针对普通方法,并且普通方法第一个参数默认是self,所以这里定义的方法也必须有self参数

推荐↓↓↓
Python编程
上一篇:你必须要了解了知识-Python反射机制 下一篇:Python super().__init__和Base.__init__的区别