零基础入门学Python(十二)—— 魔法方法(上)
零基础入门学Python系列内容的学习目录→\rightarrow→零基础入门学Python系列内容汇总。
魔法方法(上)
- 1. 构造和析构
- 1.1 _ _ init _ _(self[, ...])
- 1.2 _ _ new _ _(cls[, ...])
- 1.3 _ _ del _ _(self)
- 2. 算术运算
- 2.1 算术操作符
- 2.2 反运算
- 2.3 增量赋值运算
- 2.4 一元操作符
- 3. 简单定制
- 4. 属性访问
- 5. 描述符
- 6. 定制序列
- 7. 迭代器
- 8. 生成器
需要学习的基础知识有:构造和析构、算术运算、简单定制、属性访问、描述符、定制序列、迭代器、生成器等。因本部分内容较多,故分为上下两个篇章。
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 = xself.y = ydef getPeri(self):return(self.x + self.y) * 2def 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 “<pyshell#9>”, line 1, in < module >
cup = A()
TypeError: _ _ init _ _() should return None, not ‘str’
所以一般在需要进行初始化的时候才重写_ _ 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)
<class ‘type’>
>>>type(len)
<class ‘builtin_function_or_method’>
>>>type(dir)
<class ‘builtin_function_or_method’>
>>>type(list)
<class ‘type’>
普通的BIF应该是<class 'builtin_function_or_method'>
,而工厂函数则是<class 'type'>
。如果定义一个类:
example1: >>> class C:
pass
>>>type( C )
<class ‘type’>
类对象的类型也是type类型,其实所谓的工厂函数就是一个类对象。当你调用它们的时候,事实上就是创建一个相应的实例对象:
example2: >>> a = int(“123”)
>>> b = int(“456”)
>>> a + b
579
通过上述例子,我们可以发现对象是可以进行计算的。当求a+b
等于多少的时候,事实上Python就是在将两个对象进行相加操作。
Python的魔法方法还提供了自定义对象的数值处理,通过对下面这些魔法方法的重写,可以自定义任何对象间的算术运算。
2.1 算术操作符
表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 “<pyshell#44>”, line 1, in < module >
a + b
File “<pyshell#41>”, line 3, in _ _ add _ _
return self + other
File “<pyshell#41>”, line 3, in _ _ add _ _
return self + other
File “<pyshell#41>”, line 3, in _ _ add _ _
return self + other
[Previous line repeated 990 more times]
RecursionError: maximum recursion depth exceeded
为什么会陷入无限递归呢?问题就出在这里:
def _ _ add _ _(self, other):
return self + other
当对象涉及加法操作时,自动调用魔法方法_ _add_ _()
,上边的魔法方法写的是return 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列举了反运算相关的魔法方法。
表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
<_ _ main _ _.A object at 0x000001FE4E96CF60>
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 “<pyshell#28>”, line 1, in < module >
t1
File “D:\programming\Python37\lib\idlelib\rpc.py”, line 617, in displayhook
text = repr(value)
File “C:\Users\Administrator\Desktop\1.py”, line 5, in _ _ str _ _
return self.prompt
AttributeError: ‘MyTimer’ object has no attribute ‘prompt’
当直接执行t1
的时候,Python会调用_ _str_ _()
魔法方法,但运行程序会说这个类没有prompt
属性。这是因为我们没有执行stop()
方法,_calc()
方法就没有被调用到,所以也就没有prompt
属性的定义了,这时就需要我们使用_ _init_ _()
魔法方法了。
def __init__(self):self.prompt = "未开始计时!"self.lasted = []self.start = 0self.stop = 0
运行程序:
>>> t1 = MyTimer()
>>> t1
未开始计时!
>>> t1.start()
Traceback (most recent call last):
File “<pyshell#31>”, line 1, in < module >
t1.start()
TypeError: ‘int’ object is not callable
运行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 = 0self.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 = 0self.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 = 0self.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 = 0self.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(十二)—— 魔法方法(下)。
4. 属性访问
5. 描述符
6. 定制序列
7. 迭代器
8. 生成器
零基础入门学Python(十二)—— 魔法方法(上)相关推荐
- 零基础入门学Python(十二)—— 魔法方法(下)
零基础入门学Python系列内容的学习目录→\rightarrow→零基础入门学Python系列内容汇总. 魔法方法(下) 1. 构造和析构 2. 算术运算 3. 简单定制 4. 属性访问 5. 描述 ...
- 跟艾文学编程《零基础入门学Python》(4)Python 面向对象
作者: 艾文,计算机硕士学位,企业内训讲师和金牌面试官,公司资深算法专家,现就职BAT一线大厂. 邮箱: 1121025745@qq.com 博客:https://wenjie.blog.csdn.n ...
- 跟艾文学编程《零基础入门学Python》(1)Python 基础入门
作者: 艾文,计算机硕士学位,企业内训讲师和金牌面试官,现就职BAT一线大厂公司资深算法专家. 邮箱: 1121025745@qq.com 博客:https://wenjie.blog.csdn.ne ...
- 跟艾文学编程 《零基础入门学Python》Jupyter Notebook安装和使用
作者: 艾文,计算机硕士学位,企业内训讲师和金牌面试官,公司资深算法专家,现就职BAT一线大厂. 邮箱: 1121025745@qq.com 内容:跟艾文学编程<零基础入门学Python ...
- 零基础入门学python 第二版-《零基础入门学习Python》第二版和第一版的区别在哪里呢?...
第一版 时光荏苒,一晃间,距离<零基础入门学习 Python>出版(2016年11月)已经过去两年多了,在这段时间里, Python 逐步走入了大家的视野,这门语言因其简洁的语法风格,在云 ...
- SQL零基础入门学习(十二)
SQL零基础入门学习(SQL约束) SQL CREATE INDEX 语句 CREATE INDEX 语句用于在表中创建索引. 在不读取整个表的情况下,索引使数据库应用程序可以更快地查找数据. 索引 ...
- 【小甲鱼零基础入门学python】学习总结之字典
一.字典的创建和修改 >>> p=dict(((1,'one'),(2,'two'),(3,'Three'))) #用p=dict((()))创建 >>> p {1 ...
- python新手教程 从零开始-Python零基础从零开始学习Python十分钟快速入门
原标题:Python零基础从零开始学习Python十分钟快速入门 学习Python的,都知道Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言.Python是一种动态解释型的 ...
- 零基础学python 视频_零基础入门学习PYTHON(第2版)(微课视频版)
小甲鱼畅销图书重磅升级,针对Python 3.7,通过生动的实例,让读者在实践中理解概念,在轻松.愉快中学会Python! 本书提倡理解为主,应用为王.因此,只要有可能,小甲鱼(注:作者)都会通过生动 ...
最新文章
- Linux常用压缩与解压缩命令
- 当 Redis 发生高延迟时,到底发生了什么
- python第二章上机实践_第二章上机实践报告
- SAP UI5 getHeaderFooterOptions
- 《紫茗红菱》:“80后”成长的欢乐、疼痛与代价
- Spring的AOP-AspectJ注解方式
- Google Maps API 调用实例
- 【BZOJ1057】【codevs1428】棋盘制作,悬线法
- 程序防止多开的几种常见方法
- react视频播放组件ReactPlayer基本使用
- RabbitMQ交换机的讲解
- Cyclone IV代FPGA的可编程逻辑资源
- 百度服务获取坐标放置在天地图上实例
- 五分钟搞懂什么是红黑树(全程图解)
- 获取微信小程序的APPID及任意一个页面的路径信息
- 多元函数的极限与连续
- 【名企笔试】小米实习生招聘(风口的猪)
- 半监督学习笔记(四):熵最小化、代理变量
- nyoj304节能(区间dp)
- 后台服务被恶意脚本访问
热门文章
- python实现滑块验证功能_Python 滑块验证码
- JS - 获取选中复选框的值(checkBox)
- android wear 助手,Android Wear 2.0正式发布:将谷歌助手放进你的手表
- RFID辐射测试小结
- runtime 入门与简介
- 真00后整顿职场?公司新来了个00后卷王,3个月薪资干到20K.....
- html滚动背景色没了,css背景颜色不显示怎么解决
- 调查显示,年轻人才仍最看重“未来高收入”,“找到安全的就业”重要性急剧上升|美通社头条...
- layer弹出层 shadeClose、shade解释
- 京东上货助手怎么批量抓取淘宝商品?