Python Magic Method

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)

 in ()
      8 
      9 
---> 10 Person('Bob')


 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)

 in ()
      8 
      9 
---> 10 num_init = PositiveIntegerInit(-3)
     11 num_new = PositiveIntegerNew(-3)
     12 print("In PositiveIntegerInit() class return num is : %d" % num_init)


 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)

 in ()
----> 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)

 in ()
----> 1 hero.sing


 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)

 in ()
----> 1 hero.swim()


 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)

 in ()
----> 1 p = Person('Sunny', 'male', 90)


 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)