跳转至

魔术方法

上一节中,我们接触了 __init__ 就是魔法方法中的一种,双下划线包起来的方法,都统称为"魔术方法"。 笔者选取其中比较重要且常用的几个魔法方法,让我们一起去看下它们都有什么魔法

魔法方法 含义
__new__(cls[, ...]) new 是在一个对象实例化的时候所调用的第一个方法
__init__(self[, ...]) 构造器,当一个实例被创建的时候调用的初始化方法
__del__(self) 析构器,当一个实例被销毁的时候调用的方法
__getattr__(self, name) 该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。
__setattr__(self, name, value) 定义了对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,都允许为该属性进行赋值.有一点需要注意,实现 setattr 时要避免"无限递归"的错误。
__delattr__(self, name) delattrsetattr 很像,只是它定义的是你删除属性时的行为。实现 delattr 是同时要避免"无限递归"的错误。
__getattribute__(self, name) getattribute 定义了你的属性被访问时的行为,相比较,getattr 只有该属性不存在时才会起作用。因此,在支持 getattribute__的 Python 版本,调用__getattr 前必定会调用 getattribute``getattribute 同样要避免"无限递归"的错误。
__call__(self[, args...]) 允许一个类的实例像函数一样被调用:x(a, b) 调用 x.call(a, b)
__repr__(self) 定义当被 repr() 调用时的行为
__str__(self) 定义当被 str() 调用时的行为

构造(__new__方法) 初始化(__init__方法) 析构(__del__方法)

__init____new__ 方法,两个共同构成了 “构造函数”__new__ 是用来创建类并返回这个类的实例, 而 __init__ 只是将传入的参数来初始化该实例。 在对象生命周期调用结束时,__del__ 方法会被调用,可以将 __del__ 理解为 “构析函数”

通过代码的看一看这三个方法:

# -*- coding: UTF-8 -*-
class Person(object):
    def __new__(cls, *args, **kwargs):
        # 打印 __new__方法中的相关信息
        print('调用了 def __new__ 方法')
        print(args)
        # 最后返回父类的方法
        return super(Person, cls).__new__(cls)

    def __init__(self, name, age):
        print('调用了 def __init__ 方法')
        self.name = name
        self.age = age

    def __del__(self): 
        print('调用了 def __del__ 方法')

if __name__ == '__main__':
    p = Person('Pie', 18)

输出:

调用了 def __new__ 方法
('Pie', 18)
调用了 def __init__ 方法
调用了 def __del__ 方法

属性的访问控制

Python 没有真正意义上的私有属性,对 Python 类的封装性比较差(没有办法定义私有变量,然后定义公共的getter和setter)。 我们有时候会希望 Python 能够定义私有属性,然后提供公共可访问的 get 方法和 set 方法。Python 可通过魔术方法来实现封装。

在进行属性访问控制定义的时候,如果没有正确的使用很容易引起 “无限递归”非常重要):

#  错误用法 
def __setattr__(self, name, value): 
    self.name = value 
    # 每当属性被赋值的时候(如self.name = value), ``__setattr__()`` 会被调用,这样就造成了递归调用。 
    # 这意味这会调用 ``self.__setattr__('name', value)`` ,每次方法会调用自己。这样会造成程序崩溃。 

#  正确用法 
def __setattr__(self, name, value): 
    self.__dict__[name] = value  # 给类中的属性名分配值 

继续拓展Person:

# -*- coding: UTF-8 -*-
class Person(object):
    def __new__(cls, *args, **kwargs):
        # 打印 __new__方法中的相关信息
        print('调用了 def __new__ 方法')
        print(args)
        # 最后返回父类的方法
        return super(Person, cls).__new__(cls)

    def __init__(self, name, age):
        print('调用了 def __init__ 方法')
        # 设置name属性值,会调用 __setattr__
        self.name = name
        # 设置age属性值,会调用 __setattr__
        self.age = age

    def __del__(self):
        print('调用了 def __del__ 方法')

    def __getattr__(self, name):
        print('调用了 __getattr__ 方法')
        return super(Person, self).__getattr__(name)

    def __setattr__(self, name, value):
        print('调用了 __setattr__ 方法')
        return super(Person, self).__setattr__(name, value)

    def __delattr__(self, name):
        print('调用了 __delattr__ 方法')
        return super(Person, self).__delattr__(name)

    def __getattribute__(self, name):
        print('调用了 __getattribute__ 方法')
        return super(Person, self).__getattribute__(name)


if __name__ == '__main__':
    p = Person('Pie', 18)
    # 获取name属性值,会调用__getattribute__
    print(p.name)
    try:
        # 属性不存在, 先调用__getattribute__, 后调用__getattr__
        p.notexist
    except AttributeError:
        pass

__call__方法

_call__:可以让类的实例具有类似于函数的行为,进一步模糊了函数和对象之间的概念。

使用方式:对象后面加括号,触发执行。即:对象() 或者 类()()

class Person(object):

    def __call__(self, *args, **kwargs):
        print('call...')


person = Person()  # 将Person()的内存地址赋值给person
person()  # 内存地址()
#或者是Person()()

输出:

call...

使用__call__方法实现斐波那契数列

class Fib(object):

    def __call__(self, num):
        a, b = 1, 1
        self.lst = []

        if num <= 2:
            self.lst.append(a)
            self.lst.append(b)
        else:
            for i in range(1, num + 1):
                self.lst.append(a)
                a, b = b, a + b
        return self.lst


fib = Fib()
print(fib(10))

输出:

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

__str__方法 与 __repr__方法

str(): 使用print(对象)或者str(对象)的时候触发

repr(): 改变对象的字符串显示

如果找不到 __str__() 就会找 __repr__()方法。

%r 默认调用的是 __repr__()方法,如果是字符串会默认加上 ‘’ repr() 方法默认调用 __repr__() 方法

class Person(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        msg = 'name:%s,age:%s' % (self.name, self.age)
        return msg

    # 如果没有__str__的时候,就会执行__repr__方法
    # 如果有就不执行__repr__方法。
    def __repr__(self):
        msg = 'name----》%s,age----》%s' % (self.name, self.age)
        return msg

person = Person('Pie', 18)
print('%s' % person)
print('%r' % person)
print(person)
print(repr(person))

输出:

name:Pie,age:18
name----》Pie,age----》18
name:Pie,age:18
name----》Pie,age----》18

魔法方法很魔法

魔法方法这个名字取得就是正确,用得好就是Magic,见证奇迹的时刻,用得不规范则会让你的程序群魔乱舞。

其他一些重要的魔法请查考官方文档。