美文网首页
Python Magic Method

Python Magic Method

作者: XYZeroing | 来源:发表于2017-09-05 16:36 被阅读0次

    Magic Method

    Magic Method,魔法函数,语法为__method__,一种Python高级语法,允许用户自定义函数,并绑定到类的特殊方法上,实现用户定制类。

    1.构造和初始化

    1.1__init__

    当类被调用,实例化的第一步是创建对象。一旦对象创建了,Python检查是否实现了__init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任何特别的操作。任何所需的特定操作,都需要程序员实现__init__(),覆盖它的默认行为。如果__init__()没有实现,则返回它的对象,实例化过程完毕。如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递进去。调用类时,传进的任何参数都交给了__init__()方法。
    __init__()方法的调用发生在实例被创建之后,也就是说系统在执行该方法前,对象已经存在了

    class Person:
        def __init__(self, name):
            print('__init__ called')
            self.name = name
        
    Person('Bob')
    
    __init__ called
    
    
    
    
    
    <__main__.Person at 0x7f39bc6214e0>
    

    1.2 __new__

    __new__方法用于控制实例对象的创建过程,该方法的返回值就是类的实例对象。

    class Person:
        def __new__(cls, name):
            print('__new__ called!')
            return super(Person, cls).__new__(cls, name)
        def __init__(self, name):
            print('__init__ called')
            self.name = name
        
        
    Person('Bob')
    
    __new__ called!
    
    
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-24-4414771432da> in <module>()
          8 
          9 
    ---> 10 Person('Bob')
    
    
    <ipython-input-24-4414771432da> in __new__(cls, name)
          2     def __new__(cls, name):
          3         print('__new__ called!')
    ----> 4         return super(Person, cls).__new__(cls, name)
          5     def __init__(self, name):
          6         print('__init__ called')
    
    
    TypeError: object() takes no parameters
    

    可以看到引发了错误,但是同样的代码在Python2中不会出现,原因为:
    (1)在__new__ 被overridden或者__init__没有被overridden 的情况下,如果调用object.__new__的时候传递了除cls之外的参数将会报错;
    (2)在__new__没有被overridden或者__init__被overridden 的情况下,如果调用 object.__init__ 的时候传递了除cls之外的参数将会报错;
    (3)args,和kwds 在object.__new__除了用来判断报错,并没有什么其它用处

    shuiyutian博客有原因分析。

    将上面的代码修改:

    class Person:
        def __new__(cls, name):
            print('__new__ called!')
            return super().__new__(cls)
        def __init__(self, name):
            print('__init__ called')
            self.name = name
        
        
    Person('Bob')
    
    __new__ called!
    __init__ called
    
    
    
    
    
    <__main__.Person at 0x7f42d4361f28>
    

    正常运行!可以看到先调用了__new__来生成实例,在调用__init__来初始化实例。

    所以,__init____new__ 最主要的区别在于:

    __init__通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。
    __new__通常用于控制生成一个新实例的过程。它是类级别的方法。

    class PositiveIntegerInit(int):
        def __init__(self, value):
            super().__init__(abs(value))
            
    class PositiveIntegerNew(int):
        def __new__(cls, value):
            return super().__new__(cls, abs(value))
    
    
    num_init = PositiveIntegerInit(-3)
    num_new = PositiveIntegerNew(-3)
    print("In PositiveIntegerInit() class return num is : %d" % num_init)
    print("In PositiveIntegerNew() class return num is : %d" % num_new)
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-2-5a05be8f48e0> in <module>()
          8 
          9 
    ---> 10 num_init = PositiveIntegerInit(-3)
         11 num_new = PositiveIntegerNew(-3)
         12 print("In PositiveIntegerInit() class return num is : %d" % num_init)
    
    
    <ipython-input-2-5a05be8f48e0> in __init__(self, value)
          1 class PositiveIntegerInit(int):
          2     def __init__(self, value):
    ----> 3         super().__init__(abs(value))
          4 
          5 class PositiveIntegerNew(int):
    
    
    TypeError: object.__init__() takes no parameters
    

    不明白为什么出错!!!

    class PositiveIntegerInit(int):
        def __init__(self, value):
            object.__init__(abs(value))
            
    class PositiveIntegerNew(int):
        def __new__(cls, value):
            return super().__new__(cls, abs(value))
    
    
    num_init = PositiveIntegerInit(-3)
    num_new = PositiveIntegerNew(-3)
    print("In PositiveIntegerInit() class return num is : %d" % num_init)
    print("In PositiveIntegerNew() class return num is : %d" % num_new)
    
    In PositiveIntegerInit() class return num is : -3
    In PositiveIntegerNew() class return num is : 3
    
    class PositiveIntegerInit(int):
        def __init__(self, value):
            int.__init__(abs(value))
            
    class PositiveIntegerNew(int):
        def __new__(cls, value):
            return super().__new__(cls, abs(value))
    
    
    num_init = PositiveIntegerInit(-3)
    num_new = PositiveIntegerNew(-3)
    print("In PositiveIntegerInit() class return num is : %d" % num_init)
    print("In PositiveIntegerNew() class return num is : %d" % num_new)
    
    In PositiveIntegerInit() class return num is : -3
    In PositiveIntegerNew() class return num is : 3
    
    class PositiveIntegerInit(int):
        def __init__(self, value):
            super().__init__()
            self.value = abs(value)
            
    class PositiveIntegerNew(int):
        def __new__(cls, value):
            return super().__new__(cls, abs(value))
    
    
    num_init = PositiveIntegerInit(-3)
    num_new = PositiveIntegerNew(-3)
    print("In PositiveIntegerInit() class return num is : %d" % num_init)
    print("In PositiveIntegerNew() class return num is : %d" % num_new)
    
    In PositiveIntegerInit() class return num is : -3
    In PositiveIntegerNew() class return num is : 3
    

    1.3 __del__:不推荐使用

    这个函数要知道对象的所有引用被清除后才会执行,在对象生命周期调用结束时,__del__方法会被调用
    解构器只能被调用一次,在调用过程中不能忘记调用父类的__del__

    class C:
        def __init__(self):
            print('***********INIT************')
        def __del__(self):
            print('-----------DELETE-------------')
    
    c = C()
    del c
    
    ***********INIT************
    -----------DELETE-------------
    

    用户不需要手工管理对象,python自带的垃圾回收机制会通过引用计数来判断对象是否可以销毁。当一个对象的引用计数为0时,python解析器会自动销毁该对象,同时调用该对象的__del__方法

    __del__() 方法可能(尽管不推荐!)通过创建对它的新引用来推迟对实例的销毁。然后可以在以后删除该新引用时调用它。不能保证当解释器退出时仍然存在的对象调用 __del__() 方法。

    2.访问控制

    2.1 __getattr__

    __getattr__(self, name)
    当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正的封装的办法。

    注意,只有在没有找到属性的情况下,才调用getattr,已有的属性,比如name,不会在getattr中查找。

    class Person():
        def __init__(self, name):
            self.name = name
        def __getattr__(self, attr):
            if attr == 'age':
                return '没有年龄啊!!!'
            else:
                return 'No This Attr'
            
            
    p = Person('zhang')
    print(p.name)
    print(p.age)
    
    zhang
    没有年龄啊!!!
    
    print(p.address)
    
    No This Attr
    

    2.2 __getattribute__

    __getattribute__ 允许你自定义属性被访问时的行为,如果定义了这个方法,在引用任何的属性和方法的时候都会调用它。
    它也同样可能遇到无限递归问题(通过调用基类的 __getattribute__来避免)。

    class GetAB:
        def __getattribute__(self, attr):
            print('__getattribute__ is called')
            if attr == 'Attr':
                return 'AB'
            super().__getattribute__(attr)
        
        def output(self):
            print('OUTPUT_AB')
    
    gab = GetAB()
    
    gab.Attr
    
    __getattribute__ is called
    
    
    
    
    
    'AB'
    
    gab.output()
    
    __getattribute__ is called
    
    
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-4-d633c76a6cba> in <module>()
    ----> 1 gab.output()
    
    
    TypeError: 'NoneType' object is not callable
    

    不明白为啥出错,下面的却没有错??????

    gab.output
    
    __getattribute__ is called
    
    class Rastan:
        def __getattribute__(self, key):
            raise AttributeError           
        def swim(self):
            pass
    
    hero = Rastan()
    
    hero.sing
    
    ---------------------------------------------------------------------------
    
    AttributeError                            Traceback (most recent call last)
    
    <ipython-input-7-75ab2b0d1af6> in <module>()
    ----> 1 hero.sing
    
    
    <ipython-input-5-117434cbdbd8> in __getattribute__(self, key)
          1 class Rastan:
          2     def __getattribute__(self, key):
    ----> 3         raise AttributeError
          4     def swim(self):
          5         pass
    
    
    AttributeError: 
    
    hero.swim()
    
    ---------------------------------------------------------------------------
    
    AttributeError                            Traceback (most recent call last)
    
    <ipython-input-9-7880c861073f> in <module>()
    ----> 1 hero.swim()
    
    
    <ipython-input-6-117434cbdbd8> in __getattribute__(self, key)
          1 class Rastan:
          2     def __getattribute__(self, key):
    ----> 3         raise AttributeError
          4     def swim(self):
          5         pass
    
    
    AttributeError: 
    

    可以看到任何属性或者方法的调用都会经过__getattribute__,实现这个方法很容易出现Bug。

    2.3 __setattr__

    __setattr__(self, name, value)
    __getattr__ 不同,__setattr__ 可以用于真正意义上的封装。它允许你自定义某个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变化都定义自己的规则。然后,一定要小心使用 __setattr__

    class Person:
        name = ''
        def __setattr__(self, name, value):
            print('__setattr__ is called!!!')
            super().__setattr__(name, value)
    
    p = Person()
    
    p.name = 'Sunny'
    
    __setattr__ is called!!!
    
    p.age = 1
    
    __setattr__ is called!!!
    
    print(p.name)
    print(p.age)
    
    Sunny
    1
    
    可见,不论类是否具有属性,都可以赋值。
    

    2.4 __delattr__

    __delattr__(self, name)
    这个魔法方法和 __setattr__几乎相同,只不过它是用于处理删除属性时的行为。和__setattr__ 一样,使用它时也需要多加小心,防止产生无限递归(在 __delattr__ 的实现中调用 del self.name 会导致无限递归)。

    3.容器

    __len__(self)
    返回容器的长度,可变和不可变类型都需要实现。

    __getitem__(self, key)
    定义对容器中某一项使用 self[key] 的方式进行读取操作时的行为。这也是可变和不可变容器类型都需要实现的一个方法。它应该在键的类型错误式产生 TypeError 异常,同时在没有与键值相匹配的内容时产生 KeyError 异常。

    __setitem__(self, key)
    定义对容器中某一项使用 self[key] 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 KeyError 和 TypeError 异常。

    __iter__(self, key)
    它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 iter() 函数调用,以及在类似 for x in container: 的循环中被调用。迭代器是他们自己的对象,需要定义__iter__方法并在其中返回自己。

    __reversed__(self)
    定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法,

    __contains__(self, item)
    __contains__定义了使用 in 和 not in 进行成员测试时类的行为。你可能好奇为什么这个方法不是序列协议的一部分,原因是,如果 __contains__没有定义,Python就会迭代整个序列,如果找到了需要的一项就返回 True 。

    __missing__(self ,key)
    __missing__ 在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 d , “george” 不是字典中的一个键,当试图访问 d[“george’] 时就会调用 d.__missing__(“george”) )

    class FunctionalList:
        '''一个列表的封装类,实现了一些额外的函数式
        方法,例如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 reversed(self.values)
    
        def append(self, value):
            self.values.append(value)
    
        def head(self):
            # 取得第一个元素
            return self.values[0]
    
        def tail(self):
            # 取得除第一个元素外的所有元素
            return self.valuse[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]
    

    4.可调用对象

    __call__可以将类作为像函数调用

    class Fib(object):
        def __call__(self, num):
            a, b, fib_list = 0, 1, []
            for i in range(num):
                fib_list.append(a)
                a, b = b, a + b
            return fib_list
    
    f = Fib()
    
    print(f(20))
    
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
    
    print(f(10))
    
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    

    5.__slots__

    __slots__的目的是限制当前类所能拥有的属性,如果不需要添加任意动态的属性,使用__slots__也能节省内存。

    class Person(object):
    
        __slots__ = ('name', 'gender')
    
        def __init__(self, name, gender, score):
            self.name = name
            self.gender = gender
            self.score = score
    
    #__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__,
    #子类允许定义的属性就是自身的__slots__加上父类的__slots__
    class Student(Person):
    
        __slots__ = ('name', 'gender', 'score')
    
        def __init__(self, name, gender, score):
            self.name = name
            self.gender = gender
            self.score = score
    
    p = Person('Sunny', 'male', 90)
    
    ---------------------------------------------------------------------------
    
    AttributeError                            Traceback (most recent call last)
    
    <ipython-input-19-4f7f4074578b> in <module>()
    ----> 1 p = Person('Sunny', 'male', 90)
    
    
    <ipython-input-12-fe638787956f> in __init__(self, name, gender, score)
          6         self.name = name
          7         self.gender = gender
    ----> 8         self.score = score
          9 
         10 #__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__,
    
    
    AttributeError: 'Person' object has no attribute 'score'
    
    s = Student('Sunny', 'male', 90)
    
    print(s.name)
    print(s.gender)
    print(s.score)
    
    Sunny
    male
    90
    

    阅读

    1. Special Method Names
    2. Python Magic Methods
    3. Python 魔术方法 - Magic Method
    4. Python进阶:实例讲解Python中的魔法函数(Magic Methods)
    5. 简述 __init__、__new__、__call__方法
    6. (译)Python魔法方法指南

    相关文章

      网友评论

          本文标题:Python Magic Method

          本文链接:https://www.haomeiwen.com/subject/ejtpjxtx.html