通常通过点操作符访问对象属性,Python也提供了魔法方法来重写,从而控制对象的属性访问。
1)_ _getattr_ _(self,name)
定义当用户试图获取不存在的属性时的行为
2)_ _getattribute_ _(self,name)
定义当类的属性被访问时的行为
3)_ _setattr_ _(self,name)
定义当属性被设置时的行为
4)_ _delattr_ _(self,name)
定义当属性被删除时的行为
class C:
def _ _getattribute_ _(self,name):
print('getattribute')
return super().__getattribute_ _(name)
def _ _setattr_ _(self,name,value):
print('setattr')
return super().__setattr_ _(name,value)
def _ _delattr_ _(self,name):
print('delattr')
return super().__delattr_ _(name)
def _ _getattr_ _(self,name):
print('getattr')
>>>c = C()
>>>c.x
getattribute
getattr
>>>c.x = 1
setattr
>>>c.x
getattribute
1
>>>del c.x
delattr
>>>setattr(c,'y','Yellow')
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:
self.name = value #修改为 super()._ _setattr_ _(name,value)
def getArea(self):
return self.width * self.height
若是未修改的程序,执行r1 = Rectangle(4,5)会陷入死循环,因为构造方法试图给属性赋值,自动触发_ _setattr_ _()方法,则执行else部分,再次对属性赋值,则又触发_ _setattr_ _()方法
修改部分通过调用基类的方法来实现赋值
>>>r1 = Rectangle(4,5)
>>>r1.getArea()
20
>>>r2.square = 10
>>>r2.getArea()
100
另一种方法是对特殊属性_ _dict_ _赋值,该属性是用于以字典形式显示出当前对象的所有属性以及相对应的值
>>>r1._ _dict_ _
{'height':10,'width':10}
则修改为
else:
self._ _dict_ _[name] = value
描述符
描述符就是将某特殊类型的类(至少在类里定义了_ _get_ _(),_ _set_ _(),
_ _delete_ _()三个特殊方法的任意一个)的实例指派给另一个类的属性
1._ _get_ _(self,instance,owner)
用于访问属性,返回属性的值
2._ _set_ _(self,instance,value)
不返回任何内容,在属性设置中调用
3._ _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,instance):
print("deleting",self,instance)
MyDescriptor全都实现了这三个方法,并将类的实例指派给Test类的实例,即其是描述符类
class Test:
x = MyDescriptor()
>>>test = Test()
>>>test.x
getting
当访问x属性,Python自动调用特殊类型类的_ _get_ _()方法,参数分别是:self是描述符类自身的实例,instance是描述符拥有者所在类的实例,即test,owner是描述符拥有者所在类的本身(Test)
>>>test.x = 'man'
setting .....man
对x属性赋值时,Python自动调用_ _set_ _()方法,前两个参数同上,最后一个参数是赋的值(man)
>>>del test.x
deleting
定义属于自己的MyProperty
class MyProperty:
def _ _init_ _(self,fget = None, fset = None,fdel = None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def _ _get_ _(self,instance,owner):
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为描述符类,x为C的属性
>>>c = C()
>>>c.x = 'man'
>>>c.x
'man'
>>>c._x
'man'
>>>c._x
#AttributeError
定制序列(容器)
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)
#*args为收集参数,使用列表下标作为字典键,不用元素值(可能破坏键唯一性)
def _ _len_ _(self):
return len(self.values)
def _ _getitem_ _(self,key):
self.count[key] += 1 #键所对应的值加一
return self.values[key]
>>>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} #记录访问次数
>>>c2.count
{0:0,1:2,2:0,3:0,4:0}
迭代器
每一次重复过程称为一次迭代,每一次迭代结果作为下一次迭代的初始值,提供迭代方法的容器称为迭代器,如序列(列表,元组,字符串),字典,都支持迭代操作
>>>for i in "Fish"
print(i)
F
i
s
h
分析:"Fish"是字符串,为容器,也是迭代器,for语句触发迭代器的迭代功能,每次从容器拿出一个元素(迭代操作)
>>>links = {'dog':'gou',\
'cat':'mao'}
for each in links:
print('%s -> %s'%(each,links[each]))
dog -> gou
cat -> mao
关于迭代的BIF
对容器对象调用iter()方法,得到其迭代器,调用next()迭代器会返回下一个值,若迭代器没有值可以返回,则抛出StopIteration异常
>>>string = "Go"
>>>it = iter(string)
>>>next(it)
'G'
>>>next(it)
'o'
>>>next(it)
#StopIteration
所以for语句实际工作过程:
>>>string = "Go"
>>>it = iter(string)
>>>while True
try:
each = next(it)
except StopIteration:
break
print(each)
G
o
可见for语句自动调用next()方法和处理异常
关于迭代器的魔法方法
一个容器若是迭代器,则必须实现_ _iter_ _()魔法方法,该方法返回迭代器本身,而
_ _next_ _()方法决定迭代规则,尤为重要!
>>>class Fibs:
def _ _init_ _(self):
self.a = 0
self.b = 1
def _ _iter_ _(self):
return self
def _ _next_ _(self):
self.a,self.b = self.b,self.a + self.b
return self.a
>>>fibs = Fibs()
>>>for each in fibs:
if each < 20:
print(each)
else:
break
1
1
2
3
5
8
13
但上述例子没有终止条件,修改为
>>>class Fibs:
def _ _init_ _(self):
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
>>>fibs = Fibs(10)
>>>for each in fibs:
print(each)
1
1
2
3
5
8
生成器
不涉及魔法方法,仅通过普通的函数就可以实现
生成器实际是迭代器的一种实现,迭代器需要自己定义一个类和实现相关的方法,而生成器只需要在函数中添加yield语句
生成器的使用使得Python协同程序(生成器暂时挂起函数,函数暂停,保留函数的局部变量,之后从断点继续或重新开始)概念得以实现
普通的函数从第一行代码执行,结束于return语句,异常或者函数所有语句执行完毕,蛋函数交还控制权,则局部变量数据全部丢失
>>>def myGen():
print("activity")
yield 1
yield 2
>>>myG =myGen()
>>>next(myG)
activity
1
>>>next(myG)
2
>>>next(myG)
#StopIteration
当函数结束,异常抛出,因为for语句自动调用next()方法和处理异常,同样可作用于生成器
>>>for i in myGen():
print(i)
activity
1
2
上述的斐波那契用生成器实现
>>>def fibs():
a = 0
b = 1
while True:
a,b = b,a + b
yield a
>>>for each in fibs():
if each > 10:
break
print(each)
1
1
2
3
5
8
列表推导式
>>> a = [i for i in range(100) if not(i%2) and i%3]
#100以内能被2整除但不能被3整除
字典推导式
>>>b = {i:i % 2 == 0 for i in range(1)}
>>>b
{0:True,1:False}
集合推导式
>>>c = {i for i in[1,1,2,3]}
>>>c
{1,2,3}
不存在字符串推导式,在双引号内所有东西都成为字符串
>>>d = "i for i in 'I love running'"
>>>d
"i for i in 'I love running'"
元组推导器
>>> e = (i for i in range(5))
>>>e
<generator object <genexpr> at Ox03135300>
说明其为生成器推导器
>>>next(e)
0
>>>next(e)
1
>>>next(e)
2
for语句打印剩下内容
>>>for each in e:
print(each)
3
4
生成器作为函数参数,可直接写推导式,不用加小括号
>>>sum(i for i in range(100) if i % 2)
2500