魔术方法
上一节中,我们接触了 __init__
就是魔法方法中的一种,双下划线包起来的方法,都统称为"魔术方法"。
笔者选取其中比较重要且常用的几个魔法方法,让我们一起去看下它们都有什么魔法
。
魔法方法 | 含义 |
---|---|
__new__(cls[, ...]) | new 是在一个对象实例化的时候所调用的第一个方法 |
__init__(self[, ...]) | 构造器,当一个实例被创建的时候调用的初始化方法 |
__del__(self) | 析构器,当一个实例被销毁的时候调用的方法 |
__getattr__(self, name) | 该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。 |
__setattr__(self, name, value) | 定义了对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,都允许为该属性进行赋值.有一点需要注意,实现 setattr 时要避免"无限递归"的错误。 |
__delattr__(self, name) | delattr 与 setattr 很像,只是它定义的是你删除属性时的行为。实现 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,见证奇迹的时刻
,用得不规范则会让你的程序群魔乱舞。
其他一些重要的魔法请查考官方文档。