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)

<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魔法方法指南
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 两本不错的书: 《Python参考手册》:对Python各个标准模块,特性介绍的比较详细。 《Python核心编程...
    静熙老师哈哈哈阅读 3,354评论 0 80
  • 1、什么叫魔法方法? 魔法方法:Python解释器自动给出默认的,是可以给你的类增加魔力的特殊方法。如果你的对象实...
    Bling_ll阅读 1,034评论 0 2
  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,459评论 0 6
  • 1.1面向对象 面向对象(object-oriented ;简称: OO)至今还没有统一的概念 我这里把它定义为:...
    TENG书阅读 558评论 0 0
  • 下午的时候我翘了班,因为,我的电脑还没有装完软件。我⽤用一 个U盘笨拙地在mac和PC之间来回折腾着软件安装包,最...
    绿川阅读 138评论 0 1