零基础入门学Python系列内容的学习目录 → \rightarrow →零基础入门学Python系列内容汇总。
魔法方法(上)
需要学习的基础知识有:构造和析构、算术运算、简单定制、属性访问、描述符、定制序列、迭代器、生成器等。因本部分内容较多,故分为上下两个篇章。
1、2、3部分内容见零基础入门学Python(十二)—— 魔法方法(上)
4、5、6、7、8部分内容见零基础入门学Python(十二)—— 魔法方法(下)
1. 构造和析构
关于魔法方法的几点说明:
- 魔法方法总是被双下划线包围,例如
_ _init_ _()
; - 魔法方法是面向对象的Python的一切;
- 魔法方法的“魔力”体现在它们总能够在适当的时候被调用。
1.1 _ _ init _ _(self[, …])
_ _init_ _()
方法相当于其他面向对象编程语言的构造方法,也就是类在实例化成对象的时候首先会调用的一个方法。
举个例子:
class Rectangle: """ 定义一个矩形类, 需要长和宽两个参数, 拥有计算周长和面积两个方法, 需要对象在初始化的时候拥有“长”和“宽”两个参数, 因此需要重写__init__()方法 """ def __init__(self, x, y): self.x = x self.y = y def getPeri(self): return(self.x + self.y) * 2 def getArea(self): return self.x *self.y
运行上面代码:
>>> rect = Rectangle(3, 4)
>>> rect.getPeri()
14
>>> rect.getArea()
12
这里需要注意的是,_ _init_ _()
方法的返回值一定是None,不能是其他:
example1: >>> class A:
def _ _ init _ _(self):
return " A for A-Cup"
>>> cup = A()
Traceback (most recent call last):
File “
所以一般在需要进行初始化的时候才重写_ _ init _ _()
方法,其实,这个_ _ init _ _()
并不是实例化对象时第一个被调用的魔法方法。
1.2 _ _ new _ _(cls[, …])
关于_ _new_ _(cls[, ...])
方法:
-
_ _new_ _()
才是在一个对象实例化的时候所调用的第一个方法,它跟其他魔法方法不同,它的第一个参数不是self而是这个类(cls),而其他的参数会直接传递给_ _init_ _()
方法的。 -
_ _new_ _()
方法需要返回一个实例对象,通常是cls这个类实例化的对象,当然也可以返回其他对象。 -
_ _new_ _()
方法平时很少去重写它,一般让Python用默认的方案执行就可以了。但是当其继承一个不可变的类型的时候需要重写这个魔法方法。
class CapStr(str): def __new__(cls, string): string = string.upper() return str.__new__(cls, string)
运行上面代码:
>>> a = CapStr(“Hello,Python!”)
>>> a
‘HELLO,PYTHON!’
1.3 _ _ del _ _(self)
如果说_ _init_ _()
和_ _new_ _()
方法是对象的构造器的话,那么Python也提供了一个析构器,叫作_ _del_ _()
方法。当对象将要被销毁的时候,这个方法就会被调用。
但一定要注意的是,并非del x
就相当于自动调用x._ _del_ _()
,_ _del_ _()
方法是当垃圾回收机制回收这个对象的时候调用的。举个例子:
example1: >>> class C:
def _ _ init _ _ (self):
print(“我是_ _ init _ 方法,我被调用了…")
def _ _ del _ _ (self):
print("我是 _ del _ _方法,我被调用了…”)
>>> c1 = C()
我是_ _ init _ 方法,我被调用了…
>>> c2 = c1
>>> c3 = c2
>>> del c1
>>> del c2
>>> del c3
我是 _ del _ _方法,我被调用了…
2. 算术运算
Python2.2以后,对类和类型进行了统一,做法就是将int ()
、float()
、str()
、list()
、tuple()
这些BIF转换为工厂函数。
>>>type(int)
普通的BIF应该是,而工厂函数则是。如果定义一个类:
example1: >>> class C:
pass
>>>type( C )
类对象的类型也是type类型,其实所谓的工厂函数就是一个类对象。当你调用它们的时候,事实上就是创建一个相应的实例对象:
example2: >>> a = int(“123”)
>>> b = int(“456”)
>>> a + b
579
通过上述例子,我们可以发现对象是可以进行计算的。当求a+b
等于多少的时候,事实上Python就是在将两个对象进行相加操作。
Python的魔法方法还提供了自定义对象的数值处理,通过对下面这些魔法方法的重写,可以自定义任何对象间的算术运算。
2.1 算术操作符
表1 列举了算数运算相关的魔法方法。
魔法方法 | 含义 |
---|---|
_ _ add _ _(self, other) | 定义加法的行为:+ |
_ _ sub _ _(self, other) | 定义减法的行为:- |
_ _ mul _ _(self, other) | 定义乘法的行为:* |
_ _ truediv _ _(self, other) | 定义真除法的行为:/ |
_ _ floordiv _ _(self, other) | 定义整数除法的行为:// |
_ _ mod _ _(self, other) | 定义取模算法的行为:% |
_ _ divmod _ _(self, other) | 定义当被divmod()调用时的行为 |
_ _ pow _ _ (self, other[, modulo]) | 定义当被power()调用或**运算时的行为 |
_ _ lshift _ _ (self, other) | 定义按位左移位的行为:<< |
_ _ rshift _ _ (self, other) | 定义按位右移位的行为:>> |
_ _ and _ _ (self, other) | 定义按位与操作的行为:& |
_ _ xor _ _ (self, other) | 定义按位异或操作的行为:^ |
_ _ or _ _ (self, other) | 定义按位或操作的行为:竖线 |
举个例子,下面定义一个比较特立独行的类:
example1: >>> class New_int(int):
def _ _ add _ _(self, other):
return int. _ _ sub _ _(self, other)
def _ _ sub _ _(self, other):
return int. _ _ add _ _(self, other)
>>>a = New_int(3)
>>>b = New_int(5)
>>>a + b
-2
>>>a - b
8
如果想自己写代码,不想通过调用Python默认的方案:
example2: >>> class Try_int(int):
def _ _ add _ _(self, other):
return self + other
def _ _ sub _ _(self, other):
return self - other
>>> a = Try_int(1)
>>> b = Try_int(3)
>>> a + b
Traceback (most recent call last):
File “
为什么会陷入无限递归呢?问题就出在这里:
def _ _ add _ _(self, other):
return self + other
当对象涉及加法操作时,自动调用魔法方法_ _add_ _()
,但上边的魔法方法写的是returm self + other
,也就是返回对象本身加另外一个对象,就会又自动触发调用_ _add_ _()
方法,这样就形成了无限递归。所以,应该为下面这样:
example3: >>> class New_int(int):
def _ _ add _ _(self, other):
return int(self) + int(other)
def _ _ sub _ _(self, other):
return int(self) - int(other)
>>> a = New_int(1)
>>> b = New_int(3)
>>> a + b
4
我们通过对指定魔法方法的重写,完全可以让Python根据我们的意愿去执行结果。
example4: >>> class int(int):
def _ _ add _ _(self, other):
return int. _ _ sub _ _(self, other)
>>> a = int(‘5’)
>>> b = int(‘3’)
>>> a + b
2
通过上面的例子我们可以发现,随着对Python学习的深入,Pyhton允许我们做的事情就更多、更灵活!
2.2 反运算
表2列举了反运算相关的魔法方法。
魔法方法 | 含义 |
---|---|
_ _ radd _ _(self, other) | 定义加法的行为:+(当左操作数不支持相应的操作时被调用) |
_ _ rsub _ _(self, other) | 定义减法的行为:-(当左操作数不支持相应的操作时被调用) |
_ _ rmul _ _(self, other) | 定义乘法的行为:*(当左操作数不支持相应的操作时被调用) |
_ _ rtruediv _ _(self, other) | 定义真除法的行为:/(当左操作数不支持相应的操作时被调用) |
_ _ rfloordiv _ _(self, other) | 定义整数除法的行为://(当左操作数不支持相应的操作时被调用) |
_ _ rmod _ _(self, other) | 定义取模算法的行为:%(当左操作数不支持相应的操作时被调用) |
_ _ rdivmod _ _(self, other) | 定义当被divmod()调用时的行为(当左操作数不支持相应的操作时被调用) |
_ _ rpow _ _ (self, other[, modulo]) | 定义当被power()调用或**运算时的行为(当左操作数不支持相应的操作时被调用) |
_ _ rlshift _ _ (self, other) | 定义按位左移位的行为:<<(当左操作数不支持相应的操作时被调用) |
_ _ rrshift _ _ (self, other) | 定义按位右移位的行为:>>(当左操作数不支持相应的操作时被调用) |
_ _ rand _ _ (self, other) | 定义按位与操作的行为:&(当左操作数不支持相应的操作时被调用) |
_ _ rxor _ _ (self, other) | 定义按位异或操作的行为:^(当左操作数不支持相应的操作时被调用) |
_ _ ror _ _ (self, other) | 定义按位或操作的行为:竖线(当左操作数不支持相应的操作时被调用) |
不难发现,这里的反运算魔法方法跟之前的算术运算符保持相对应,不同之处就是反运算的魔法方法多了一个“r
”,例如,_ _ add _ _()
就对应_ _ radd _ _()
。举个例子:
example1: >>> a + b
# 如果a对象的_ _ add _ _ ()方法没有实现或者不支持相应的操作,那么Python就会自动调用b的_ _ radd _ _ ()方法
example2: >>> class Nint(int):
def _ _ radd _ _(self, other):
return int. _ _ sub _ _(other, self)
>>> a = Nint(5)
>>> b = Nint(3)
>>> a + b
8
# 由于a对象默认有_ _ add _ _ ()方法,所以b的_ _ radd _ _ ()没有执行
>>> 1 + b
-2
关于反运算,要注意一点:对于a + b
,b
的_ _radd_ _(self, other)
的self
是b
对象,other
是a
对象。看如下一个例子:
example3: >>> class Nint(int):
def _ _ rsub _ _(self, other):
return int. _ _ sub _ _(self, other)
>>> a = Nint(5)
>>> 3 - a
2
所以对于注重操作数顺序的运算符(例如减法、除法、移位),在重写反运算魔法方法的候,就一定要注意顺序问题了。
2.3 增量赋值运算
Python也有大量的魔术方法可以来定制增量赋值语句,增量赋值其实就是一种偷懒的形式,它将操作符与赋值来结合起来。例如:
example1: >>> a = a + b
# 写成增量赋值的形式就是:
>>> a += b
2.4 一元操作符
一元操作符就是只有一个操作数的意思;像a+b
这样,加号左右有a
、b
两个操作数叫作二元操作符。
Python支持的一元操作符主要有_ _neg_ _()
(表示正号行为),_ _pos_ _()
(定义负号行为),_ _abs_ _()
(定义当被abs()
调用时的行为,就是取绝对值的意思),还有一个_ _invert_ _()
(定义按位取反的行为)。
3. 简单定制
基本要求:
- 定制一个计时器的类。
-
start
和stop
方法代表启动计时和停止计时。 - 假设计时器对象
t1
,print(t1)
和直接调用t1
均显示结果。 - 当计时器未启动或已经停止计时,调用
stop
方法会给予温馨的提示。 - 两个计时器对象可以进行相加:
t1 + t2
。 - 只能使用提供的有限资源完成。
这里需要限定只能使用哪些资源,因为Python的模块是非常多的。
需要下面的资源:
- 使用
time
模块的localtime
方法获取时间。 -
time.localtime
返回struct_time
的时间格式。 - 表现类:
_ _str_ _ ()
和_ _repr_ _()
魔法方法。
example1: >>> class A:
def _ _ str _ _ (self):
return “Hello, Python!”
>>> a = A()
>>> print(a)
Hello, Python!
>>> a
example2: >>> class B:
def _ _ repr _ _ (self):
return “Hello, Python!”
>>> b = B()
>>> b
Hello, Python!
开始来编写代码:
import time as tclass MyTimer: # 开始计时 def start(self): self.start = t.localtime() print("计时开始…") # 停止计时 def stop(self): self.stop = t.localtime() print("计时结束!")
写好基础之后,进行计算。.localtine()
返回的是一个时间元组的结构,只需要前边6个元素,然后将stop
的元素依次减去start
对应的元素,将差值存放在一个新的列表里:
#停止计时 def stop(self): self.stop = t.localtime() self._calc() print("计时结束!") # 内部方法,计算运行时间 def _calc(self): self.lasted=[] self.prompt = "总共运行了" for index in range(6): self.lasted.append(self.stop[index]- self.start[index]) self.prompt += str(self.lasted[index]) print(self.prompt)
运行程序:
>>> t1 = MyTimer()
>>> t1.start()
计时开始…
>>> t1.stop()
总共运行了00001-52
计时结束!
已经基本实现计时功能了,接下来需要完成"print(t1)和直接调用t1均显示结果",那就要通过重写_ _str_ _ ()
和_ _repr_ _()
魔法方法来实现:
def __str__(self): return self.prompt __repr__ = __str__
运行程序:
>>> t1 = MyTimer()
>>> t1.start()
计时开始…
>>> t1.stop()
计时结束!
>>> t1
总共运行了000005
上面的程序如下运行的时候会出现问题:
>>> t1 = MyTimer()
>>> t1
Traceback (most recent call last):
File “
当直接执行t1
的时候,Python会调用_ _str_ _()
魔法方法,但运行程序会说这个类没有prompt
属性。这是因为我们没有执行stop()
方法,_calc()
方法就没有被调用到,所以也就没有prompt
属性的定义了,这时就需要我们使用_ _init_ _()
魔法方法了。
def __init__(self): self.prompt = "未开始计时!" self.lasted = [] self.start = 0 self.stop = 0
运行程序:
>>> t1 = MyTimer()
>>> t1
未开始计时!
>>> t1.start()
Traceback (most recent call last):
File “
运行t1.start()
又出错了,Python 这里抛出了一个异常:TypeError: ‘int’ object is not callable。这是说Python认为start
是一个整型变量,而不是一个方法。因为在_ _init_ _()
方法里,我们也命名了一个叫作self.start
的变量,如果类中的方法名和属性同名,属性会覆盖方法。所以我们需要把所有的self.start
和self.end
都改为self.begin
和self.end
。
我们再将显示时间000005改为“年月日小时分钟秒”去显示,所以程序修改为:
def __init__(self): self.unit = ['年', '月', '日', '小时', '分钟', '秒'] self.prompt = "未开始计时!" self.lasted = [] self.begin = 0 self.end = 0 # 计算运行时间 def _calc(self): self.lasted=[] self.prompt = "总共运行了" for index in range(6): self.lasted.append(self.end[index]- self.begin[index]) if self.lasted[index]: self.prompt += (str(self.lasted[index]) + self.unit[index])
运行程序:
>>> t1=MyTimer()
>>> t1.start()
计时开始…
>>> t1.stop()
计时结束!
>>> t1
总共运行了4秒
然后再在适当的地方增加温馨提示:
# 开始计时 def start(self): self.begin = t.localtime() self.prompt = "提示:请先调用stop()结束计时!" print("计时开始…") #停止计时 def stop(self): if not self.begin: print("提示:请先调用start()开始计时!") else: self.end = t.localtime() self._calc() print("计时结束!") # 计算运行时间 def _calc(self): self.lasted=[] self.prompt = "总共运行了" for index in range(6): self.lasted.append(self.end[index] - self.begin[index]) if self.lasted[index]: self.prompt += (str(self.lasted[index]) + self.unit[index]) # 为下一轮计算初始化变量 self.begin = 0 self.end = 0
最后,再重写一个魔法方法_ _add_ _()
,让两个计时器对象相加会自动返回时间的和。
def __add__(self, other): prompt = "总共运行了" result =[] for index in range(6): result.append(self.1asted[index] + other.lasted[index]) if result[index]: prompt += (str(result[index]) + self.unit[index]) return prompt
整个代码如下:
import time as tclass MyTimer: def __init__(self): self.unit = ['年', '月', '日', '小时', '分钟', '秒'] self.prompt = "未开始计时!" self.lasted = [] self.begin = 0 self.end = 0 def __str__(self): return self.prompt __repr__ = __str__ # 开始计时 def start(self): self.begin = t.localtime() self.prompt = "提示:请先调用stop()结束计时!" print("计时开始…") #停止计时 def stop(self): if not self.begin: print("提示:请先调用start()开始计时!") else: self.end = t.localtime() self._calc() print("计时结束!") # 计算运行时间 def _calc(self): self.lasted=[] self.prompt = "总共运行了" for index in range(6): self.lasted.append(self.end[index] - self.begin[index]) if self.lasted[index]: self.prompt += (str(self.lasted[index]) + self.unit[index]) # 为下一轮计算初始化变量 self.begin = 0 self.end = 0 def __add__(self, other): prompt = "总共运行了" result =[] for index in range(6): result.append(self.lasted[index] + other.lasted[index]) if result[index]: prompt += (str(result[index]) + self.unit[index]) return prompt
运行程序:
>>> t1=MyTimer()
>>> t1
未开始计时!
>>> t1.stop()
提示:请先调用start()开始计时!
>>> t1.start()
计时开始…
>>> t1
提示:请先调用stop()结束计时!
>>> t1.stop()
计时结束!
>>> t1
总共运行了10秒
>>> t2 = MyTimer()
>>> t2.start()
计时开始…
>>> t2.stop()
计时结束!
>>> t2
总共运行了8秒
>>> t1 + t2
‘总共运行了18秒’
后半部分内容见零基础入门学Python(十二)—— 魔法方法(下)。