7、面向对象之魔术方法

1、__new__

     用于创建对象,但一般无需重新定义该方法。

2、__init__

    初始化创建好的对象,初始化的前提是:“给实例对象赋值”。

    如果不定义__init__方法,系统会提供一个默认的__init__方法。

    如果定义了带参的__init__方法,系统不创建默认的__init__方法。 __init__只能返回None,一般不写return

3、__del__

# 析构方法,垃圾回收机制
class Person:
    def __del__(self):				#重新定义__del__()方法
        print('销毁对象:{0}'.format(self))
p1=Person()
p2=Person()
del p2						#主动调用del函数,销毁p2对象
print('程序结束')					#程序执行完毕后,会再次调用del,销毁p1对象

4、__call__

    可调用对象:凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数 callable。例函数,内置函数、类都属于可调用对象。

    在类中重新定义 __call__ 方法,那么实例对象也将成为一个可调用对象,例:

class SalaryAcount:
    def __call__(self,salary):
        year = salary*12
        return dict(month=salary,year=year)
s=SalaryAcount()
print(s(10000))

5、__str__

    直接打印实例化对象时会调用该方法,该方法返回的必须是字符串

6、__enter__与__exit__

    出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量

__exit__(self, exc_type, exc_val, exc_tb)

   exit()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

     如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

7、__setitem__ 、__getitem__、__delitem__

class Tag:
    def __init__(self):
        self.change={'python':'This is python'}
    def __getitem__(self, item):
        print('这个方法被调用')
        return self.change[item]
a=Tag()
print(a['python'])

8、__getattr__与__getattribute__

    重载__getattr__方法对类及其实例未定义的属性有效。如果访问的属性存在,就不会调用__getattr__方法。这个属性的存在,包括类属性和实例属性

class ClassA:
    x = 'a'
    def __init__(self):
        self.y = 'b'
    def __getattr__(self, item):
        return '__getattr__'
a = ClassA()
print(a.x)                # 输出结果 a
print(a.y)                # 输出结果 b
print(a.z)                # 输出结果 __getattr__

    通过类名直接调用类属性不会调用__getattribute__

    通过实例化对象调用实例属性或者类属性都会首先调用__getattribute__,无论类存在与否

    内置getattr和hasattr也会触发这个魔法方法__getattribute__

class ClassA:
    x = 'a'
    def __init__(self):
        self.y = 'b'
    def __getattribute__(self, item):
        return '__getattribute__'
print(ClassA.x)
a = ClassA()
print(a.x)              # 输出结果 __getattribute__
print(a.y)              # 输出结果 __getattribute__
print(a.z)              # 输出结果 __getattribute__

  在__getattribute__代码块中,再次执行属性的获取操作时,会再次触发__getattribute__方法的调用,代码将会陷入无限递归

class ClassA:
    x = 'a'
    def __getattribute__(self, item):
        print('__getattribute__')
        return self.item   # 再次出现属性的获取操作,会再次触发__getattribute__的调用,相当于return self.__getattribute__(item) 
a = ClassA()
a.x

  为了避免无限递归,应该把获取属性的方法 __getattribute__指向一个更高的超类,例如object。

class ClassA:
    x = 'a'                                   # 类属性
    def __getattribute__(self, item):
        print('__getattribute__')
        return super().__getattribute__(item)  # 或者使用return object.__getattribute__(self, item)
a = ClassA()
print(a.x)                                     # 输出__getattribute__

  当同时定义__getattribute__和__getattr__时,__getattr__方法不会再被调用,除非显示调用__getattr__方法或引发AttributeError异常。 

class A():
    x = 1
    def __getattribute__(self, item):
        print('正在调用属性{}'.format(item))
        if item != 'x':
            raise AttributeError
        return super().__getattribute__(item)
    def __getattr__(self, item):
        print('正在调用属性{}'.format(item))
        return 'aa'
a = A()
print(a.x)

9、__setattr__

     试图给属性赋值时自动调用该方法

class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def __setattr__(self, key, value):
        self.__dict__[key] = value       #此处不能写成self.key = value,会导致无线循环
p = Person('kuangfeng',20)
p.gender = 'man'
print(p.__dict__)

  会执行三次print函数,是因为在__init__方法中,对象A初始化时给属性name和age赋值时,触发了__setattr__方法。 使用该方法是同样需要十分小心避免无限循环陷阱

      如果定义__setattr__方法的同时定义了__getattribute__方法,那么在修改__dict__字典中的键值对时, 由于调用了self.__dict__属性,同样会触发__getattribute__方法,使用时应格外小心

class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def __setattr__(self, key, value):
        print('执行__setattr__')
        self.__dict__[key] = value
    def __getattribute__(self, item):
        print('执行__getattribute__')
        print('item:{}'.format(item))            #此处的item是__dict__
        return super().__getattribute__(item)
p = Person('kuangfeng',20)
p.gender = 'man'
print(p.__dict__)

10、__delattr__

    定义当一个属性被删除时的行为

11、__get__、__set__、__del__描述器

  一个类,如果只定义了 __get__() 方法,而没有定义 __set__(), __delete__() 方法,则认为是非数据描述符;反之,则成为数据描述符

      __getattribute__(), 无条件调用

      数据描述符:由第一条触发调用 (若人为的重载了该 __getattribute__() 方法,可能会调职无法调用描述符

      实例对象的字典(若与描述符对象同名,会被覆盖哦)

      类的字典

      非数据描述符

      父类的字典

       __getattr__() 方法

      对象属性的访问顺序:实例属性------------>类属性---------->父类属性------------->__getattr__

class Desc():
    def __get__(self, instance, owner):
        print('Desc __get__')
        print('self:{}'.format(self))
        print('instance:{}'.format(instance))
        print('owner:{}'.format(owner))
        print('=' * 50)

    def __set__(self, instance, value):
        print('Desc __set__')
        print('self:{}'.format(self))
        print('instance:{}'.format(instance))
        print('value:{}'.format(value))
        print('=' * 50)

class TestDesc(Desc):
    x = Desc()

t = TestDesc()
t.x
以下为输出内容:
Desc __get__
self:<__main__.Desc object at 0x000001A8044C8430>               #self为Desc的实例对象,其实就是TestDesc的类属性x
instance:<__main__.TestDesc object at 0x000001A8044D6580>       #instance为TestDesc的实例对象,其实就是t
owner:<class '__main__.TestDesc'>                               #owner为TestDesc类
==================================================

  原因: t为TestDesc的实例,当访问t.x时,首先访问TestDesc的__getattrbute__()方法,发现没有之后去类TestDesc中访问,访问到了类属性x 其次,判断类属性x为一个描述符,此时,它就会做一些变动了,将TestDesc.x转化为TestDesc.__dict__['x'].__get__(t,TestDesc)来访问 然后进入类Desc中的__get__()方法,进行相应的操作

class Desc(object):
    def __init__(self, name):
        self.name = name
    def __get__(self, instance, owner):
        print("__get__...")
        print('name = ', self.name)
        print('=' * 40, "\n")
class TestDesc(object):
    x = Desc('x')
    def __init__(self):
        self.y = Desc('y')      # 把描述符(Desc类)的对象变成实例属性
t = TestDesc()
t.x
t.y
#以下为输出结果:
__get__...
name =  x
========================================
<__main__.Desc object at 0x03FB0088>            #没有打印t.y的信息,但至少证明了y是Desc类的实例对象

  原因: 访问t.y时,首先会调用TestDesc(即Owner)的__getattribute__()方法,就是TestDesc.__getattribute__() 先来访问实例属性,找到y,又发现属性y为一个描述符,于是将t.y转化成TestDesc.__dict__['y'].__get__(t, TestDesc) 但是,实际上TestDesc并没有y这个属性,y是属于实例对象的,所以,只能忽略了。

 

  

上一篇:c# – 如何区分USB和软盘设备?


下一篇:Improving Generalization in Reinforcement Learning with Mixture Regularization