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 . . . . . .