Python魔术方法

在Python中,所有以__双下划线包起来的方法,都统称为"魔术方法"。

魔术方法

1. new, init, del, 垃圾回收机制

当我们调用x = SomeClass()的时候,首先调用new来创建类并返回这个类的实例, 接着调用init将传入的参数来初始化该实例。newinit共同构成了"构造函数".

__new__在创建一个实例的过程中必定会被调用,但__init__就不一定,比如通过pickle.load的方式反序列化一个实例时就不会调用__init__。

__new__方法总是需要返回该类的一个实例,而__init__不能返回除了None的任何值。

在对象的生命周期结束时, del会被调用,可以将del理解为"析构函数"。del定义的是当一个对象进行垃圾回收时候的行为。

1.1. new, init

参考here

# class A(object): python2 必须显示地继承object
class A:

    def __new__(cls):
        print("__new__ ")
        self = super(A, cls).__new__(cls)
        print(self)
        return self

    def __init__(self):
        print("__init__ ")
        print(self)
        super(A, self).__init__()

    def __call__(self):  # 可以定义任意参数
        print('__call__ ')

a = A()
__new__ 
<__main__.A object at 0x000001A4A0595D48>
__init__ 
<__main__.A object at 0x000001A4A0595D48>

从实验结果可以分析得到,new方法的返回值就是类的实例对象self,然后传递到init方法中用于初始化实例对象。如果new方法中不返回值,那么init方法就不会被调用,因为实例对象都没有创建出来,就不会调用init方法。

python中垃圾回收机制
无论是手动销毁,还是 Python 自动帮我们销毁,都会调用 del() 方法
Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。该方法的核心思想是:每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,实例对象计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用 del() 方法将其回收。

class CLanguage:
    def __init__(self):
        print("调用 __init__() 方法构造对象")

    def __del__(self):
        print("调用__del__() 销毁对象,释放其空间")


clangs = CLanguage()
# 添加一个引用clangs对象的实例对象
cl = clangs
del clangs
print("***********")

out

调用 __init__() 方法构造对象
***********
调用__del__() 销毁对象,释放其空间

以上面程序中的 clangs 为例,实际上构建 clangs 实例对象的过程分为 2 步,先使用 CLanguage() 调用该类中的 init() 方法构造出一个该类的对象(将其称为 C,计数器为 0),并立即用 clangs 这个变量作为所建实例对象的引用( C 的计数器值 + 1)。在此基础上,又有一个 clang 变量引用 clangs(其实相当于引用 CLanguage(),此时 C 的计数器再 +1 ),这时如果调用del clangs语句,只会导致 C 的计数器减 1(值变为 1),因为 C 的计数器值不为 0,因此 C 不会被销毁(不会执行 del() 方法)。而在程序的最后,也就是程序执行完毕之后,此时会自动回收变量,才会执行del()方法。

对于计数器为 0 的实例对象,Python 会自动将其视为垃圾进行回收。

下面的例子是手动调用del,此时当del的对象引用为0的时候,会执行del方法。

class CLanguage:
    def __init__(self):
        print("调用 __init__() 方法构造对象")

    def __del__(self):
        print("调用__del__() 销毁对象,释放其空间")


clangs = CLanguage()
# 添加一个引用clangs对象的实例对象
cl = clangs
del clangs
print("***********")
del cl
print("-------")

out

调用 __init__() 方法构造对象
***********
调用__del__() 销毁对象,释放其空间
-------

1.2. new实现单例模式

new方法用于创建对象,是一个工厂函数,专用于生产实例对象。如果想要设计单利模式,可以使用下面的代码。

class singleton:
    _singleton = None
    def __new__(cls, *args, **kwargs):
        if not cls._singleton:
        # 将_singleton对象绑定在singleton这个类上面,因此就可以创建出单个实例。
            cls._singleton = super().__new__(cls, *args, **kwargs)
        return cls._singleton

    def __init__(self):
        pass

sing = singleton()
sing2 = singleton()

id(sing)  == id(sing2)

2. call

首先,了解一个概念可调用对象,自定义的函数,内置函数和类都属于可调用对象,但凡是可以把一对括号应用到某个对象身上都可称为可调用对象,可以使用callable来判断。

# class A(object): python2 必须显示地继承object
class A:
    def __init__(self):
        pass

    def __call__(self):  # 可以定义任意参数
        print('__call__ ')

a = A()
callable(a) # True

如果在类中实现了call方法,那么实例对象也将成为一个可调用对象。
此时可以使用a()来调用可调用对象。

a()  # 此时就会执行__call__方法,输出__call__

2.1. 一种应用场景-基于类的装饰器

类可以记录数据,而函数不行,因此可以在函数上增加一个类装饰器,从而使用类来帮助函数记录东西

class Counter:

    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

@Counter   # foo = Counter(foo),此时的foo相当于类Counter的实例。
def foo():
    pass

for i in range(5):
    foo()     # 调用实例对象,此时会执行__call__()方法。

print(foo.count)

上面的例子中可以看出结果中记录了函数的执行次数。

3. 2.getattr, setattr, delattr,getattribute 属性访问

  1. getattr(self, name)该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。
  2. setattr(self, name, value) 是实现封装的解决方案,它定义了你对属性进行赋值和修改操作时的行为。
    不管对象的某个属性是否存在,它都允许你为该属性进行赋值,因此你可以为属性的值进行自定义操作。有一点需要注意,实现setattr时要避免"无限递归"的错误
  3. delattr(self, name)与setattr很像,只是它定义的是你删除属性时的行为。实现delattr是同时要避免"无限递归"的错误。
  4. getattribute(self, name)定义了你的属性被访问时的行为,相比较,getattr只有该属性不存在时才会起作用。因此,在支持getattribute的Python版本,调用getattr前必定会调用 getattributegetattribute同样要避免"无限递归"的错误。需要提醒的是,最好不要尝试去实现getattribute,因为很少见到这种做法,而且很容易出bug。

无限递归:当采用self.name赋值的时候,会调用setattr方法,然后又出现self.name,从而产生无限递归的效果。

def __setattr__(self, name, value):
    self.name = value
    # 每一次属性赋值时, __setattr__都会被调用,因此不断调用自身导致无限递归了。

正确写法

def __setattr__(self, name, value):
    self.__dict__[name] = value
class Access(object):

    def __getattr__(self, name):
        print('__getattr__')
        return super().__getattr__(name)

    def __setattr__(self, name, value):
        print('__setattr__')
        return super().__setattr__(name, value)

    def __delattr__(self, name):
        print('__delattr__')
        return super().__delattr__(name)

    def __getattribute__(self, name):
        print('__getattribute__')
        return super().__getattribute__(name)

access = Access()
access.attr1 = True  # __setattr__调用
access.attr1  # 属性存在,只有__getattribute__调用
try:
    access.attr2  # 属性不存在, 先调用__getattribute__, 后调用__getattr__
except AttributeError:
    pass
del access.attr1  # __delattr__调用

out

__setattr__
__getattribute__
__getattribute__
__getattr__
__delattr__

4. 描述器 get, set, delete

一个类中定义get,set ,delete中的一个或者多个的时候,此时该类的实例就可以称为是描述器。 描述器是@property,super,静态方法,类方法等的背后的实现机制。
现在我们定义一个类来表示距离,它有两个属性: 米和英尺。

class Meter(object):
    '''Descriptor for a meter.'''
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Foot(object):
    '''Descriptor for a foot.'''
    def __get__(self, instance, owner):
        return instance.meter * 3.2808
    def __set__(self, instance, value):
        instance.meter = float(value) / 3.2808

class Distance(object):
    meter = Meter()
    foot = Foot()

d = Distance()
print(d.meter, d.foot)  # 0.0, 0.0
d.meter = 1
print(d.meter, d.foot)  # 1.0 3.2808
d.meter = 2
print(d.meter, d.foot)  # 2.0 6.5616

在上面例子中,在还没有对Distance的实例赋值前, 我们认为meter和foot应该是各自类的实例对象, 但是输出却是数值。这是因为get发挥了作用. 我们只是修改了meter,并且将其赋值成为int,但foot也修改了。这是set发挥了作用.

描述器(meter、foot)不能独立存在, 它需要被另一个所有者类(Distance)所持有。

4.1. 描述器对象

上面的例子中,meter,foot为描述器对象,Distance为拥有者。描述器对象可以访问到其拥有者实例的属性,比如例子中Foot的instance.meter(这里的instance就是Distance的实例。)

一个类要成为描述器,必须实现 get,set, delete 中的至少一个方法。下面简单介绍下:

__get__(self, instance, owner)
print(isinstance(instance, owner))  True
参数instance是拥有者类的实例,参数owner是拥有者类本身。__get__在其拥有者对其读值的时候调用。就上面的例子而言,owner表示Distance这个类, instance相当于Distance类的实例对象。
__set__(self, instance, value)在其拥有者对其进行修改值的时候调用。
__delete__(self, instance)在其拥有者对其进行删除的时候调用

5. 构造自定义容器

在Python中,常见的容器类型有: dict, tuple, list, string。
其中tuple, string是不可变容器,dict, list是可变容器。
可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。

比如定义了l = [1, 2, 3]和t = (1, 2, 3)后, 执行l[0] = 0是可以的,但执行t[0] = 0则会报错。

如果我们要自定义一些数据结构,使之能够跟以上的容器类型表现一样,那就需要去实现某些协议。这里的协议跟其他语言中所谓的"接口"概念很像,一样的需要你去实现才行,只不过没那么正式而已。

5.1. 方法

len(self)需要返回数值类型,以表示容器的长度。该方法在可变容器和不可变容器中必须实现。

getitem(self, key)当你执行self[key]的时候,调用的就是该方法。该方法在可变容器和不可变容器中也都必须实现。
调用的时候,如果key的类型错误,该方法应该抛出TypeError;
如果没法返回key对应的数值时,该方法应该抛出ValueError。

setitem(self, key, value)当你执行self[key] = value时,调用的是该方法。

delitem(self, key)当你执行del self[key]的时候,调用的是该方法。

iter(self)该方法需要返回一个迭代器(iterator)。当你执行for x in container: 或者使用iter(container)时,该方法被调用。

reversed(self)如果想要该数据结构被內建函数reversed()支持,就还需要实现该方法。

contains(self, item)如果定义了该方法,那么在执行item in container 或者 item not in container时该方法就会被调用。
如果没有定义,那么Python会迭代容器中的元素来一个一个比较,从而决定返回True或者False。

missing(self, key)dict字典类型会有该方法,它定义了key如果在容器中找不到时触发的行为。
比如d = {'a': 1}, 当你执行d[notexist]时,d.missing('notexist')就会被调用。

5.2. 自定义不可变容器

如果要自定义不可变容器类型,只需要定义lengetitem方法;

# 自定义容器

class Function:
    def __init__(self, values=None):
        if values:
            self.values = values
        else:
            self.values = []

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        return self.values[key]

f = Function([1,2,3])
f[0]

5.3. 自定义可变容器

如果要自定义可变容器类型,还需要在不可变容器类型的基础上增加定义setitemdelitem

# 自定义容器

class Function:
    def __init__(self, values=None):
        if values:
            self.values = values
        else:
            self.values = []

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]

f = Function([1,2,3])
f[0] = 2
del f[0]
f[0]

5.4. 一个比较复杂的例子

# -*- coding: utf-8 -*-
class FunctionalList:
    ''' 实现了内置类型list的功能,并丰富了一些其他方法: head, tail, init, last, drop, take'''

    def __init__(self, values=None):
        if values is None:
            self.values = []
        else:
            self.values = values

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]

    def __iter__(self):
        return iter(self.values)

    def __reversed__(self):
        return FunctionalList(reversed(self.values))

    def append(self, value):
        self.values.append(value)
    def head(self):
        # 获取第一个元素
        return self.values[0]
    def tail(self):
        # 获取第一个元素之后的所有元素
        return self.values[1:]
    def init(self):
        # 获取最后一个元素之前的所有元素
        return self.values[:-1]
    def last(self):
        # 获取最后一个元素
        return self.values[-1]
    def drop(self, n):
        # 获取所有元素,除了前N个
        return self.values[n:]
    def take(self, n):
        # 获取前N个元素
        return self.values[:n]

6. 参考

  1. https://segmentfault.com/a/1190000007256392 [未完成]
  2. delhere
全部评论

相关推荐

儆珒:结果一问,人家都不认识他,装
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务