Python基础教程(第2版)第九章 魔法方法、属性和迭代器

1.新式类

由于一些特性(如属性和super函数)不会在老式的类上起作用,所以需要确保类是新型的:

        1.把赋值语句__metaclass__=type放在文件的最开始

__metaclass__=type
class OldStyle():
	# more_code_here
	pass

        2.直接或间接地子类化内建类object(或其他一些新式类)

class NewStyle(object):
	# more_code_here
	pass

        3.还可以在自己的类的作用域中对__metaclass__变量赋值

class Foo(object):
    __metaclass__=type

在Python3中没有“旧式”的类,也不需要显示地子类化object或者将元类设置为type,所有的类都会隐式地成为object的子类

如果没有明确超类的话,就会直接子类化,否则会间接子类化

2.构造方法:__init__(self)

用来初始化新创建对象的状态,当一个对象被创建后,会立即调用构造方法

class FooBar:
	def __init__(self):
		self.somevar=42
f=FooBar()
print(f.somevar)	#42

对应的,Python中有一个析构方法:__del__,会在对象被垃圾回收之前调用

        由于该方法发生调用的具体时间是不可知的,因此要尽量避免使用

2.1 方法重写

        每个类都可能拥有一个或多个超类,当一个子类的对象调用某个方法(属性)但在子类中没有找到该方法时,会去它的超类中寻找

class A:
	def hello(self):
		print("Hello,I'm A")
class B(A):
	pass
a=A()
b=B()
a.hello()	#Hello,I'm A
b.hello()	#Hello,I'm A

我们可以重写一些超类的方法来自定义继承的行为

class A:
	def hello(self):
		print("Hello,I'm A")
class B(A):
	def hello(self):
		print("Hello,I'm B")
a=A()
b=B()
a.hello()	#Hello,I'm A
b.hello()	#Hello,I'm B

构造方法也可以进行重写,但是相比于普通方法,它可能会遇到特别的问题:

        如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则对象可能不会被正确地初始化

        也就是说,大多数子类不仅要拥有自己的初始化代码,还要拥有超类的初始化代码

class Bird:
	def __init__(self):
		self.hungry=True
	def eat(self):
		if self.hungry:
			print("Aaaah...")
			self.hungry=False
		else:
			print("No,thanks")
b=Bird()
b.eat()	#Aaaah...
b.eat()	#No,thanks
class SongBird(Bird):
	def __init__(self):
		self.sound="Squawk"	#新的构造方法没有任何关于初始化hungry属性的代码
	def sing(self):
		print(self.sound)
sb=SongBird()
sb.sing()	#Squawk
sb.eat()	#Traceback (most recent call last):
# 				File "unit09.py", line 59, in <module>
# 					sb.eat()
# 				File "unit09.py", line 44, in eat
# 					if self.hungry:
# 			AttributeError: 'SongBird' object has no attribute 'hungry'

2.2 调用未绑定的超类构造方法

绑定方法:在调用一个实例的方法时,该方法的self参数会被自动绑定到实例上

未绑定方法:如果直接调用类的方法(比如Bird.__init__),那么就没有实例会被绑定,这样就可以自由地提供需要的self参数

通过将当前的实例作为self参数提供给未绑定方法,子类就能够使用其超类构造方法的所有实现

class Bird:
	def __init__(self):
		self.hungry=True
	def eat(self):
		if self.hungry:
			print("Aaaah...")
			self.hungry=False
		else:
			print("No,thanks")
class SongBird(Bird):
	def __init__(self):
		Bird.__init__(self)	#调用超类的构造方法
		self.sound="Squawk"
	def sing(self):
		print(self.sound)
sb=SongBird()
sb.sing()	#Squawk
sb.eat()	#Aaaah...
sb.eat()	#No,thanks

本节介绍的内容主要是历史遗留问题,在Python3版本中,使用super函数的方法更简单明了

2.3super函数

当前类和对象可以作为super函数的参数使用

super函数返回的是一个super对象,这个对象负责进行方法解析,当对其特性进行访问时,它会查找所有的超类(以及超类的超类),直到找到所需的特性(或者引发一个AttributError异常)为止

__metaclass__=type	#super函数只在新式类中起作用
class Bird:
	def __init__(self):
		self.hungry=True
	def eat(self):
		if self.hungry:
			print("Aaaah...")
			self.hungry=False
		else:
			print("No,thanks")
class SongBird(Bird):
	def __init__(self):
		super(SongBird,self).__init__()	#使用super函数,调用SongBird类的超类中的方法
		self.sound="Squawk"
	def sing(self):
		print(self.sound)
sb=SongBird()
sb.sing()	#Squawk
sb.eat()	#Aaaah...
sb.eat()	#No,thanks

此时的__init__方法以绑定方式被调用

3.自定义序列和映射类型

在Python中只是简单地要求对象遵守几个给定的规则,而Python中的多态性是基于对象的行为

        规则说明了应该实现何种方法和这些方法应该做什么

3.1 基本的序列和映射规则

序列和映射是对象的集合,为了实现它们基本的行为(规则),如果对象是不可变的,那么
就需要使用两个魔法方法:

        __len__(self):返回集合中所含项目的数量。对于序列来说,是元素的个数;对于映射来说,则是键-值对的数量

        __getitem__(self,key):返回与所给键对应的值。对于序列来说,键应该是0到n1的整数(或者负数);对于映射来说,可以使用任何种类的键

如果是可变的,则需要使用4个,另外两个为:

        __setitem__(self,key,value):按一定的方式存储和key相关的value

        __delitem__(self,key):对部分对象使用del语句时调用,同时必须删除和元素相关的键        

对于这些方法,还有一些附加要求

        对于一个序列来说,如果键是负整数,那么要从末尾开始计数

        如果键是不合适的类型(例如对序列使用字符串作为键),会引发TypeError异常

        如果序列的索引是正确的类型,但超出了范围,会引发IndexError异常

#算术序列
def checkIndex(key):	#关键字异常检测
	if not isinstance(key,(int)):	#python3中没有long这一数据类型
		raise TypeError
	if key<0:
		raise IndexError
class ArithmeticSequence:
	def __init__(self,start=0,step=1):
		self.start=start
		self.step=step
		self.changed={}
	def __getitem__(self,key):
		checkIndex(key)
		try:
			return self.changed[key]
		except KeyError:
			return self.start+key*self.step
	def __setitem__(self,key,value):
		checkIndex(key)
		self.changed[key]=value
s=ArithmeticSequence(1,2)
print(s[4])	#9
s[4]=2
print(s[4])	#2
print(s[5])	#11

3.2 子类化列表、字典和字符串

如果希望实现一个和内建类型行为相似的类,可以考虑子类化内建类型

class CounterList(list):
	def __init__(self,*args):
		super(CounterList,self).__init__(*args)
		self.counter=0	#记录访问次数
	def __getitem__(self,index):
		self.counter+=1
		return super(CounterList,self).__getitem__(index)
cl=CounterList(range(10))
print(cl.counter)	#0
print(cl[4]+cl[2])	#6
print(cl.counter)	#2

4.属性:通过访问器定义的特性

4.1 property函数:创建属性

property函数可以用0、1、2、3或者4个参数来调用,这4个参数分别叫做fget、fset、fdel和doc,对应的是取值方法,设置值方法和删除特性以及文档字符串

__metaclass__=type
class Rectangle:
	def __init__(self):
		# self.width=0
		# self.height=0
		pass
	def setSize(self,size):
		self.width,self.height=size
	def getSize(self):
		return self.width,self.height
	size=property(getSize,setSize)
r=Rectangle()
r.width=10
r.height=5
print(r.size)	#(10, 5)
r.size=150,100
print(r.width)	#150

实际上,property函数不是应该真正的函数,而是拥有很多特殊方法的类,正是这些方法完成了所有的工作

4.2 静态方法和类成员方法

静态方法和类成员方法在创建时分别被装入Staticmethod类型和Classmethod类型的对象中

静态方法的定义没有self参数,且能够被类本身直接调用

类成员方法在定义时需要cls参数,可以直接用类的具体对象调用,cls参数是自动被绑定到类的

__metaclass__=type
class MyClass:
	def smeth():
		print("This is a static method")
	smeth=staticmethod(smeth)
	def cmeth(cls):
		print("This is a clas meth of",cls)
	cmeth=classmethod(cmeth)
MyClass.smeth()	#This is a static method
MyClass.cmeth()	#This is a clas meth of <class '__main__.MyClass'>

在Python2.4中,引入了装饰器:能对任何可调用的对象进行包装,既能够用于方法也能
用于函数

使用@操作符,在方法(或函数)的上方将装饰器列出,从而指定一个或者多个装饰器(多个装饰器在应用时的顺序与指定顺序相反)

__metaclass__=type
class MyClass:
	@staticmethod
	def smeth():
		print("This is a static method")
	@classmethod
	def cmeth(cls):
		print("This is a clas meth of",cls)
MyClass.smeth()	#This is a static method
MyClass.cmeth()	#This is a clas meth of <class '__main__.MyClass'>

静态方法和类成员方法在Python中井不是很重要,大部分情况下可以使用函数或者绑定方法代替

4.3 __getattr__、__setattr__和它的朋友们

当property方法不能使用时,可以用旧式类实现属性

        __getattribute__ (self,name):当特性name被访问时自动被调用(只能在新式类中使用)

        __getattr__ (self,name):当特性name被访问且对象没有相应的特性时被自动调用

        __setattr__ (self,name,value):当试图给特性name赋值时会被自动调用

        __delattr__(self,name):当试图删除特性name时被自动调用

class Rectangle:
	def __init__(self):
		self.width=0
		self.height=0
	def __setattr__ (self,name,value):
		if name=="size":	#__setattr__方法在所涉及的特性不是size时也会调用,所有需要判断name
			self.width,self.height=value
		else:
			self.__dict__[name]=value	#__dict__包含一个所有实例属性的字典
	def __getattr__ (self,name):
		if name=="size":	#__getattr__方法只在普通的特性没被找到的时候调用
			return self.width,self.height
		else:
			raise AttributeError
r=Rectangle()
r.width=10
r.height=5
print(r.size)	#(10, 5)
r.size=150,100
print(r.width)	#150

5.迭代器

能对实现了__iter__方法的对象进行迭代,即一个实现了__iter__方法的对象是可迭代的,而一个实现了next方法的对象则是迭代器

        next方法在调用时不需要任何参数

        在调用next方法时,迭代器会返回它的下一个值

        如果next方法被调用但是迭代器没有值可以返回,则会引发一个StopIteration异常

在Python3中,迭代器对象应该实现__next__方法而不是next,而新的内建对象next()可以用于访问该方法,即next(it)等同于之前的it.next()

class Fibs:
	def __init__(self):
		self.a=0
		self.b=1
	def __next__(self):
		self.a,self.b=self.b,self.a+self.b
		return self.a
	def __iter__(self):
		return self
fibs=Fibs()
for f in fibs:
	if f >1000:
		print(f)	#1597
		break

内建函数iter可以从可迭代对象中获得迭代器

it=iter([1,2,3])
print(next(it))	#1
print(next(it))	#2

在大部分能使用序列的情况下,都能使用迭代器或可迭代对象替换(索引、分片等操作不行)

还可以使用list构造方法显示地将迭代器转化为列表:

class TestIterator:
	value=0
	def __next__(self):
		self.value+=1
		if self.value>10:
			raise StopIteration
		return self.value
	def __iter__(self):
		return self
ti=TestIterator()
print(list(ti))	#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

6.生成器

6.1 创建生成器

生成器是由生成器的函数和生成器的迭代器两部分组成,生成器的函数包含yield语句,而生成器的迭代器是函数的返回部分

当它被调用时,在函数体中的代码不会被执行,而是会返回一个迭代器每次请求一个值,就会执行生成器中的代码,直到遇到一个yield 者return语句

        yield语句意味着应该生成一个值

        return语句意味着生成器要停止执行(不再生成任何东西,它只有在一个生成器中使用时才能进行无参数调用)

生成器的函数和普通函数也有很大的差别:

        不像return那样返回值,而是每次产生多个值,每(遇到yield语句)产生一个值,函数就会被冻结(即函数停在那点等待被激活)

        函数被激活(请求一个值)后就从停止的那点开始执行生成器中的代码

        因此可以通过在生成器上选代来使用所有的值

def flatten(nested):
	for sublist in nested:
		for element in sublist:
			yield element
nested=[[1,2],[3,4],[5]]
for num in flatten(nested):
	print(num)	#1  2  3  4  5
print(list(flatten(nested)))	#[1, 2, 3, 4, 5]

6.2 递归生成器

当不知道待处理数据内有几层嵌套时,可以使用递归生成器:

def flatten(nested):
	try:
		for sublist in nested:
			for element in flatten(sublist):	#递归
				yield element
	except TypeError:	#递归的基本情况:对一个数字进行迭代
		yield nested
print(list(flatten([[[1],2],3,4,[5,[6,7]],8])))	#[1, 2, 3, 4, 5, 6, 7, 8]

上述例子会产生一个问题:如果nested是一个类似于字符串的对象则不会引发TypeError,为了处理这种情况,必须在生成器的开始处添加一个检查语句

def flatten(nested):
	try:
		try:
			nested+''	#检查语句:通过和字符串拼接来检查
		except TypeError:
			pass
		else:
			raise TypeError
		for sublist in nested:
			for element in flatten(sublist):	#递归
				yield element
	except TypeError:	#递归的基本情况:对一个数字进行迭代
		yield nested
print(list(flatten([[[1],2],3,4,[5,[6,7]],8])))	#[1, 2, 3, 4, 5, 6, 7, 8]

一般而言,我们也不应该在flatten函数中对类似于字符串的对象进行迭代:

        1.要实现的是将类似于字符串的对象当成原子值,而不是当成应被展开的序列

        2.对它们进行迭代会导致无穷递归,因为一个字符串的第一个元素是另一个长度为1
的字符串,而长度为1的字符串的第一个元素就是字符串本身

6.3 生成器方法

Python2.5后,可以通过send方法在开始运行后为生成器提供值

        在生成器的内部函数中,yield作为表达式而不是函数来使用

        使用send方法只有在生成器挂起(yield函数被第一次执行)之后才有意义,如果在此之前需要给生成器提供更多消息,只能使用生成器函数的参数

        如果实在想要对刚刚启动的生成器使用send方法,可以将None作为其参数进行调用

def repeater(value):
	while  True:
		new=(yield value)
		if new is not None:
			value=new
r=repeater(42)
print(next(r))	#42
r.send("Hello,world!")
print(next(r))	#Hello,world!

throw方法(在yield表达式中)用于在生成器内引发一个异常

close方法(在需要的时候也会由Python垃圾收集器调用)用于停止生成器

6.4 模拟生成器

生成器在Python旧版本中是不可用的,我们可以用普通函数来模拟生成器

def flatten(nested):
	result=[]	#函数体开始出添加result=[]
	try:
		try:
			nested+''
		except TypeError:
			pass
		else:
			raise TypeError
		for sublist in nested:
			for element in flatten(sublist):
				result.append(element)	#用result.append(...)替换yield ...
	except TypeError:
		result.append(nested)	#用result.append(...)替换yield ...
	return result	#在函数末尾添加return result
print(list(flatten([[[1],2],3,4,[5,[6,7]],8])))	#[1, 2, 3, 4, 5, 6, 7, 8]

7.八皇后问题

#八皇后问题
def conflict(state,nextX):	#nextX:下一个皇后的x坐标
	nextY=len(state)	#下一个皇后的y坐标
	for i in range(nextY):
		if abs(state[i]-nextX) in (0,nextY-i):
			return True	#冲突
	return False
def queens(num=8,state=()):
	for pos in range(num):
		if not conflict(state,pos):
			if len(state)==num-1:
				yield (pos,)
			else:
				for result in queens(num,state+(pos,)):
					yield (pos,)+result
def prettyprint(solution):
	def line(pos,length=len(solution)):
		return '. '*(pos)+'X '+'. '*(length-pos-1)
	for pos in solution:
		print(line(pos))
import random
prettyprint(random.choice(list(queens())))
#		. . . X . . . . 
#		. . . . . X . . 
#		. . . . . . . X 
#		. . X . . . . . 
#		X . . . . . . . 
#		. . . . . . X . 
#		. . . . X . . . 
#		. X . . . . . . 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值