- 什么是魔法方法
- 构造和析构
- __init__方法
- __new__方法
- __del__方法
- 工厂函数
- 通过对魔法方法的重写,还可以自定义对象间的算术运算
- 反运算方法
- 实现一个计时器的类
- time模块的localtime方法获取时间,time.localtime返回struct_time的时间格式
- __str__方法
- __repr__方法实现输入一个对象,就返回一个字符串的功能
- 属性访问
- 调用基类的方法避免死循环问题
- 给一个特殊属性dict赋值
- 描述符(property属性)
- 容器的定制
- 迭代器( 待补充)
什么是魔法方法
- 魔法方法总是被双下划綫包围,例如__init__
- 魔法方法是面向对象的Python的一切,功能强大
- 魔法方法的魔力体现在他们总是能够在适当的时候被调用
构造和析构
__init__和__new__方法是Python的构造器
__init__方法
类似于其他语言的构造方法(C++,Java),即类在实例化对象时首先会调用的方法。
在类定义时,为何有时写__init__方法,有时却没有? A:根据需求看是否需要传值。没有写__init__时Python会自动的调用它。
# 求长方形(x,y)的周长和面积
>>> class Rectangle:
def __init__(self,x,y):
self.x=x # 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,否则会抛出TypeError异常
>>> class A:
def __init__(self):
return 'A'
>>> a=A()
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
a=A()
TypeError: __init__() should return None, not 'str'
__new__方法
它的第一个参数是class(这个类),__new __在__init__之前被调用,如果new后面有参数,会将参数原样传给__init__方法。极少重写new方法,但是当继承不可变类型且需要进行修改时,需要通过重写new方法实现
如将不可变的str类型实现全部大写的功能
__del__方法
当对象被销毁时,就会自动调用该方法,_del_ 方法是垃圾回收机制,即当变量的引用计数为零时,垃圾回收机制会自动的回收和销毁,这时才会调用该对象的__del__方法
>>> class C:
def __init__(self):
print('正在调用ini方法。。。')
def __del__(self):
print('正在调用del方法。。。')
>>> c1=C()
正在调用ini方法。。。
>>> c2=c1
>>> c3=c2
>>> del c3
>>> del c2
>>> del c1
正在调用del方法。。。
工厂函数
工厂函数看上去有点像函数,实质上他们是类,当你调用它们时,实际上是生成了该类型的一个实例,就像工厂生产货物一样。当类在定义的时候称为类,定义结束后就成为类对象,而工厂函数实际上就是类对象。
>>> a=int('123') # 实例化一个int对象,返回一个实例后的对象,将123传进去
>>> a
123
>>> b=int('456')
>>> a+b
579
通过对魔法方法的重写,还可以自定义对象间的算术运算
举一个神奇的“栗子”:
>>> 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 # '+'运算符调用__add__方法,实际上是减法,因为在定义时重写了__add__方法
-2
>>> a-b # '-'运算符调用__sub__方法
8
错误的写法会导致无限递归
对于上述错误的改进:
反运算方法
如果右操作数的类型是左操作数类型的子类,并且该子类提供了操作的反射方法,则该反射方法将在左操作数的非反射方法之前被调用。
>>> class Nint(int):
def __radd__(self,other):
return int.__sub__(self,other)
>>> b=Nint(3)
>>> a=Nint(5)
>>> a+b
8
>>> 1+b # 这里self是b,other是‘1’
2
重写反运算要注意顺序问题
>>> class Nint(int):
def __rsub__(self,other):
return int.__sub__(self,other)
>>> a=Nint(5)
>>> 3-a # a是self,other是‘3’
2
>>> class Nint(int):
def __rsub__(self,other):
return int.__sub__(other,self)
>>> a=Nint(5)
>>> 3-a
-2
实现一个计时器的类
详细需求如下图:
要实现以上需求,需要用到的内容如下:
time模块的localtime方法获取时间,time.localtime返回struct_time的时间格式
详见 time 模块详解
__str__方法
>>> class A():
def __str__(self):
return 'Python'
>>> a=A()
>>> print(a) # 字符串输出时会自动调用__str__魔法方法,如果返回值,会返回内存地址
__repr__方法实现输入一个对象,就返回一个字符串的功能
>>> class B():
def __repr__(self):
return 'hello world'
>>> b=B()
>>> b
hello world
用 print 函数打印a对象会隐式地调用对象的 str() 方法,直接输出对象会隐式的调用对象的 repr() 方法。 第一步:实现最基本最主要的三个方法,代码如下所示:
第二步:假设计时器对象t1,print(t1)和直接调用t1的显示结果,注意在init方法定义变量,否则可能会抛出AttributeError异常
使用init方法初始化变量需要注意变量命名问题,如果定义的属性名和方法名相同,属性会覆盖方法,抛出TypeError
修改如下:
第三步:当计时器未启动或已经停止计时,调用stop方法会给予提示
第四步:两个计时器对象可以进行相加:t1 + t2
import time as t
class MyTimer():
def __init__(self):
self.unit=['年','月','天','小时','分钟','秒']
self.prompt='未开始计时'
self.lasted=[]
self.begin=0
self.end=0
def __str__(self):
return self.prompt
__repr__=__str__ # 把__str__赋值过去,str指向什么,repr就会指向什么
# 重写__add__方法,两个计时器相加返回一个计算结果
def __add__(self,other):
self.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
# 开始计时
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]) # localtime 取前六个位置,将每个位置的值相减,存放在lased列表中
if self.lasted[index]:
self.prompt += (str(self.lasted[index]) + self.unit[index])
# 为下一轮计时初始化变量
self.begin = 0
self.end=0
print(self.prompt)
属性访问
重写Python魔法方法可以控制对象的属性访问
举个例子来更好的理解他们的触发情况:
>>> class C:
def __getattribute__(self,name):
print('getattribute')
return super().__getattribute__(name)
def __getattr__(self,name):
print("getattr")
def __setattr__(self,name,value):
print('setattr')
super().__setattr__(name,value)
def __delattr__(self,name):
print('delattr')
super().__delattr__(name)
>>> c=C()
>>> c.x
getattribute # 获取属性的第一步
getattr # 获取不存在的属性时才会访问
>>> c.x=1
setattr
>>> c.x
getattribute
1
>>> del c.x
delattr
** 死循环陷阱**
举一个“生动形象的例子”:
写一个矩形类,默认有宽和高两个属性,如果为一个叫square的属性赋值,那么说明这是一个正方形,值就是正方形的边长,此时宽和高都应该等于边长。
调用基类的方法避免死循环问题
class Rectangle:
def __init__(self,width=0,height=0):
self.width=width
self.height = height
def __setattr__(self,name,value):
if name=='square':
self.width=vlaue
self.height=value
else:
super().__setattr__(name,value)
def getArea(self):
return self.width*self.height
给一个特殊属性dict赋值
class Rectangle:
def __init__(self,width=0,height=0):
self.width=width
self.height = height
def __setattr__(self,name,value):
if name=='square':
self.width=value
self.height=value
else:
self.__dict__[name]=value # 法二
# 法一:super().__setattr__(name,value)
# self.name=value
def getArea(self):
return self.width*self.height
描述符(property属性)
描述符就是实现了以下三种具有描述符属性的方法(至少一种就算描述符), peoperty本质上是一个描述符类。
>>> class MyDescriptor:
def __get__(self,instance,owner):
print("geting..",self,instance,owner)
def __set__(self,instance,value):
print("seting...",self,instance,value)
def __delete__(self,instance):
print("del....",self,instance)
>>> class Test:
x=MyDescriptor() # MyDescriptor类是描述符类
>>> test=Test()
>>> test.x
geting.. <__main__.MyDescriptor object at 0x0000021E74CEAE50> <__main__.Test object at 0x0000021E74CEAD60> <class '__main__.Test'>
>>> test # 对应instance
<__main__.Test object at 0x0000021E74CEAD60>
>>> Test # 对应owner
<class '__main__.Test'>
>>> test.x='X'
seting... <__main__.MyDescriptor object at 0x0000021E74CEAE50> <__main__.Test object at 0x0000021E74CEAD60> X
>>> del test.x
容器的定制
必须知道的容器类型的协议
- 如果需要定制的容器是不可变的话,只需要定义__len__()和__getitem__()方法
- 如果需要定制可变容器的话,除了__len__()和__getitem__(),还需要定义__setitem__()和__delitem__()
练习 编写一个不可改变的自定义列表,要求记录列表中每个元素被访问的次数
class Countlist:
def __init__(self,*args):
self.values=[x for x in args]
self.count={}.fromkeys(range(len(self.values)),0) # 用fromkeys进行列表初始化且全部初始化为零
def __len__(self):
return len(self.values)
def __getitem__(self,key):
self.count[key] +=1
return self.values[key]
执行结果如下
=========================== RESTART: F:/Pythonspace/countlist.py ===========================
>>> c1=Countlist(1,3,5,7,9)
>>> c2=Countlist(2,4,6,8,10)
>>> c1[1]
3
>>> c2[1]
4
>>> c1[1]+c2[1]
7
>>> c1.count
{0: 0, 1: 2, 2: 0, 3: 0, 4: 0} # 1被访问了两次
>>> c1[1]
3
>>> c1.count
{0: 0, 1: 3, 2: 0, 3: 0, 4: 0}
迭代器( 待补充)
列表,元组,字符串,字典都是大家熟知的迭代器,文件也支持迭代操作,通常使用for语句来迭代他们,迭代器主要是通过如下两个BIF来实现的,
- iter()
- next() (主要指定迭代器的规则,用迭代器实现斐波那契数列)
>>> string="Python"
>>> it = iter(string)
>>> next(it)
'P'
>>> next(it)
'y'
>>> next(it)
't'
>>> next(it)
'h'
>>> next(it)
'o'
>>> next(it)
'n'
>>> next(it)
Traceback (most recent call last):
File "<pyshell#294>", line 1, in <module>
next(it)
StopIteration
解析for语句工作原理
>>> string="Python"
>>> it =iter(string)
>>> while True:
try:
each=next(it)
except StopIteration:
break
print(each)
P
y
t
h
o
n
详见 Python 魔法方法详解