魔法方法
在python中,有一些内置好的特定的方法,这些方法在进行特定的操作时会自动被调用,称之为魔法方法。
构造和析构
魔法方法总是被双下横线包围,例如__init__;
是面向对象的Python的一切;
他们总是能在适当的时候被调用。
-
__init__(self[, …])
相当于其他编程语言的构造方法,类在实例化对象的时候首先会调用的一个方法。
-
__new__(cls[, …])
init并不是实例化对象调用的第一个方法,new方法才是,它的第一个参数是cls,通常情况下是返回cls类的对象,也可以返回其他类的对象。
new方法是极少去重写它的,Python会默认执行,但是当需要继承一个不可变类型又需要修改的时候,那么就需要重写了。
-
__del__(self)
当对象将要被销毁的时候这个方法会自动被调用,但是
del x
并不等于调用了
x.__del__()
del方法是当垃圾回收机制,即当没有任何变量去引用这个对象的时候,垃圾回收机制会自动销毁,这时才会调用对象的self方法。
注意:内置的__del__()方法并不是发生del操作的时候就会调用,当对象生成后,所有对它的引用都被del后才会启动垃圾回收机制,才会调用__del__()方法。
算数运算
在Python2.2之前类和类型是分开的,类是属性和方法的封装,类型是如整型、浮点型、字符串这些类型,但是在Python2.2后,试图对两者进行统一,做法就是将int、float、string、list、tuple这些内置函数通常转化为工厂函数。
- 什么是工厂函数
原因: 当a+b,识别到加法会先调用前者a的add,返回self+other,即返回了a+b,就又运行了加法add,进入了无限递归。
所以在实现的时候,一定要注意避免出现无限递归的情况。
稍作修改
- 一些算数运算
方法 | 相应二元算数运算符 |
---|---|
__add__(self, other) | 定义加法的行为:+ |
__sub__(self, other) | 定义减法的行为:- |
__mul__(self, other) | 定义乘法的行为:* |
__truediv__(self, other) | 定义真除法的行为:/ |
__floordiv__(self, other) | 定义整数除法的行为:// |
__mod__(self, other) | 定义取模算法的行为:% |
__divmod__(self, other) | 定义当被divmod()调用时的行为【divmod(a, b)返回值是一个元组(a//b, a%b)】 |
__pow__(self, other[, modulo]) | 定义当被pow()调用或**运算时的行为 |
__lshift__(self, other) | 定义按位左移位的行为:<< |
__rshift__(self, other) | 定义按位右移位的行为:>> |
__and__(self, other) | 定义按位与操作的行为:& |
__xor__(self,other) | 定义按位异或操作的行为:^ |
__or__(self, other) | 定义按位或操作的行为:| |
通过对指定魔法方法的重写,可以让Python根据自己的意图来实现程序
- 反运算
这里是3-1,并不是1-3,如果想让1-3,那么就要互换int.sub()中self, other的位置,举例说明
定制一个简单的类
- 需要的资源
(1)使用time模块的localtime方法获取时间
(2)time.localtime返回struct_time的时间格式
(3)表现你的类:重写__str__和__repr__
- 程序MyTimer
import time as t
class MyTimer():
def __str__(self):
return self.prompt
__repr__ = __str__
# 开始计时
def start(self):
self.start = t.localtime()
print("计时开始...")
# 停止计时
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])
但是这里有一个问题就是,如果定以后直接调用,就会报错
因为这时,prompt还没有被定义,这时就需要所有属于实例对象的变量先在init中定义
import time as t
class MyTimer():
# 添加init定义
def __init__(self):
self.prompt = "未开始计时!"
self.lasted = []
self.start = 0
self.stop = 0
def __str__(self):
return self.prompt
__repr__ = __str__
# 开始计时
def start(self):
self.start = t.localtime()
print("计时开始...")
# 停止计时
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])
这时在执行虽然不会直接调用t1出错了,但是运行起来却又出现“整型不能被调用”问题,这里是由于在init中将self.start定义为0导致,因为类的方法名和属性名一样时,属性会覆盖方法,这里就认为start是属性。
这时只需要将start和stop改一下名字即可
并且在这里改变一下显示的方式和累加计时
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__
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
# 开始计时
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]: # 为0不显示
self.prompt += str(self.lasted[index]) + self.unit[index]
# 为下一轮计时初始化变量
self.begin = 0
self.end = 0
- 代码存在的问题
(1)生成的时间会存在负数的情况
(2)精度不够,只能到秒
属性访问
(1)直接访问属性
(2)通过getattr()访问
(3)利用property(),以属性的方式访问属性
- __getattr__(self, name)
定义当用户试图获取一个不存在的属性时的行为 - __getattribute__(self, name)
定义当该类的属性被访问时的行为 - __setattr__(self, name, value)
定义当一个属性被设置时的行为 - __delattr__(self, name)
定义当一个属性被删除时的行为
>>> 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)
- 练习
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.name = value
def getArea(self): # 获得面积
return self.width * self.height
# 输入 r1 = Rectangle(4, 5)
# 这样写会出现一个无限递归,因为执行__init__中的self.width和self.height赋值语句,会触发__setattr__中的else后的语句self.name = value,再重复调用__setattr__,这样就会无限递归下去
# =====================================================================================================
# 下面进行改进
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:
super().__setattr__(name, value)
def getArea(self): # 获得面积
return self.width * self.height
另一种改进方法就是给一个特殊属性dict,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] = vallue
def getArea(self): # 获得面积
return self.width * self.height
描述符
描述符就是将某种特殊类型的类的实例指派给另一个类的属性。
__get__(self, instance, owner)
# 用于访问属性,返回属性的值
__set__(self, instane, value)
# 将在属性分配操作中调用,不返回任何内容
__delete__(self, instance)
# 控制删除操作,不返回任何内容
- 实例
class MyDescriptor
def __get__(self, instance, owner):
print("getting...", self, instance, owner)
def __set__(self, instance, value):
print("setting...", self, instance, value)
def __delete__(self, instnce):
print("deleting...", self, instance)
class Test:
x = MyDescriptor()
【ps:这里改用spyder编辑了,输入处显示方式改变,实际操作同python idle相同】
这里就是将某种特殊类型的类(MyDescriptor)的实例(MyDescriptor())指派给另一个类(Test)的属性(x),就说明MyDescriptor就是x的描述符。
实例化对象后,用text.x强制打印
可以看到打印出三个参数,第一个是self的参数描述符类MyDescriptor本身的实例,第二个是instance的参数类的拥有者Test的实例test,第三个就是拥有者类Test本身
验证一下
对实例化对象进行赋值,出现赋值调用set的特殊方法,打印self、instance和value
del同理,打印self和instance
- 定义一个MyProperty
之前提到的property其实就是一个描述符
class MyProperty:
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, value):
return self.fget(instance)
def __set__(self, instance, value):
self.fset(instance, value)
def __delete__(self, instance):
self.fdel(instance)
class C:
def __init__(self):
self._x = None
def getX(self):
return self._x
def setX(self, value):
self._x = value
def delX(self):
del self._x
x = MyProperty(getX, setX, delX)
同样的这里将MyProperty的实例MyProperty()指派给类C的属性x,对类C的实例对象c的x属性赋值,调用setX返回c._x再进行操作。
- 练习
class Celsius:
def __init__(self, value = 26.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Fahrenheit:
def __get__(self, instance, owner):
return instance.cel * 1.8 +32
def __set__(self, instance, value):
instance.cel = (float(value) - 32) / 1.8
class Temperature:
cel = Celsius()
fah = Fahrenheit()
定制容器
- 协议
协议Protocols相似于接口,规定了必须要定义的方法。而在Python中协议更像是一种指南。 - 容器类型的协议
(1)定制不可变容器
只需定义__len__()和__getitem__()方法
(2)定制可变容器
除__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]
迭代器
提供迭代方法的容器称之为迭代器。
通常的迭代器有序列、列表、元组、字符串、字典、文件。
- for语句迭代
- 字典迭代
- 关于迭代操作,Python提供了两个BIF内置函数inter()和next()
inter() 即iteration
对于容器对象调用iter就得到它的迭代器,调用next()就会返回下一个值,当迭代器没有值可以返回了,Python就会抛出一个“StopIteration”的异常,此时迭代结束。
这样就可以知道for语句是如何执行的
利用while语句来模拟for语句的执行
- 迭代器的魔法方法
# 两个魔法方法分别对应两个BIF容器的实现
iter()
--> __iter__() # 返回迭代器本身
next()
--> __next__() # 决定迭代器的迭代规则
- 实现斐波那契数列的打印
class Fibs:
def __init__(self, n=10):
self.a = 0
self.b = 1
self.n = n
def __iter__(self):
return self
def __next__(self):
self.a, self.b = self.b, self.a + self.b
if self.a > self.n:
raise StopIteration
return self.a
生成器
生成器并不涉及魔法方法、类和对对象,只通过普通的函数实现。生成器实际上是迭代器的一种实现。
生成器延续了Python简洁的特点,并且使协同程序的概念得以实现,协同程序就是可以运行的独立函数调用,函数可以暂停或挂起,并在需要的时候从程序离开的地方继续或者重新开始。
Generator 实例
def myGen():
print("生成器被执行!")
yield 1
yield 2
# 一旦一个函数中出现yield语句
# 那么就说明这个函数被定义为生成器
# yield就相当于普通函数中的return
# 和return的区别:
# 出现yield,就将yield后的参数返回,并暂停在yield处
- 实现斐波那契数列
def libs():
a = 0
b = 1
while True:
a, b = b, a + b
yield a
# 由于有yield,所以while True不会变成死循环
- 推导式
(1)列表推导式
(2)字典推导式
有“:”的是字典,没有的是集合
(3)集合推导式
(4)没有字符串推导式
(5)元组(tuple)推导式
打印元组e发现e是一个生成器推导式
生成器推导式如果作为函数的参数,是可以直接写推导式,不需要加括号
【100以内不能被2整除的整数和】