Python的学习
高级特性
切片
迭代
列表生产式
L=[]
for x in range(1,11):
L.append(x*x)
但是循环太繁琐,而列表生成式则可以用一行语句代替生成上面的list:
[x*x for x in range(1,11)]
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
[x*x for x in range(1,11) if x%2==0]
还可以使用两层循环,可以生成全排列
[m+n for m in ‘ABC’ for n in ‘XYZ’]
result:
[‘AX’,’AY’,’AZ’,’BX’,’BY’,’BZ’,’CX’,’CY’,’CZ’]
运用列表生成器,可以写出非常简洁的代码
import os
[d for d in os.listdir(‘.’)]
[‘.emacs.d’,‘.ssh’,...]
for循环其实可以同时使用两个甚至多个变量
for k,v in d.items():
print(k,’=’,v)
裂变生成式也可以使用两个变量来生产list:
[k+’=‘+v for k,v in d.items()]
运用列表生产式,可以快速生产list,可以通过一个list推导出l另一个list,而代码十分简洁。
生成器
通过列表生成器,我们可以直接创建一个list。但是,受限于内存,列表容量肯定是有限的。然而,创建一个100万的元素的列表,不仅需要占用很大的空间。
所以,如果列表元素的可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素?这样我们就不需要大量的内存空间,从而节省了大量的内存空间。
在python中,一遍推导一遍计算的机制,称为生成器。generator
要创建一个generator,有很多种方法。第一种方法很简单,只要把列表生成器[]
改成()
.就可以创建一个generator。
L=[x*x for x in range(10)]
g=(x*x for x in range(10))
创建L和g区别仅在于最外面层的【】和(),【】是一个列表,然而()则是一个generator。
可以通过next()函数来获得下一个返回值。
斐波拉契列用列
def fib(max):
n,a,b=0,0,1
while n<max:
print b
a,b=b,a+b
n=n+1
return ‘done’
仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
def fib(max):
n,a,b=0,0,1
while n<max:
yield n
a,b=b,a+b
n=n+1
return ‘done’
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
举一个简单的例子,定义一个gener,依次返回数字1,3,5:
def odd():
print(‘step 1’)
yield 1
print(’step 2’)
yield 3
print(’step 3’)
yield 5
调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:
o=odd()
next(o)
next(o)
next(o)
可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就终端,下次有继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错,
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获stopIteration错误,返回值包含在StopIterationd的value中:
while True:
try:
x=next(g)
print(‘g:’,x)
exception StopInteration as e:
break
迭代器
我们已经知道,可以直接作用域for循环的数据了下有以下几种:
一类是集合数据类:
- 一类是集合数据类型,如list、tuple、dict、set、str等
- 一类是generator,包括生成器和带yield的generator function
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象
from collections import Iterable
isinstance([],Iterable)
而生产器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一值了。
可以被next()函数调用并不断返回下一个值的对象陈给迭代器:Iterator.
isinstance(iter([]),Iterator)
函数式编程
高阶函数
map/reduce
python内建了map()和reduce()函数
我们先看map。map()函数接收两个参数,一个是函数,一个是iterable,map将闯入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator是惰性序列。因此通过list()函数让它把整个序列都计算出来并返回一个list。
在看reduce的用法。reduce把一个函数作用一个序列[x1,x2,…],这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。
filter
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True和False决定保留还是丢失该元素。
filter的作用是从一个序列中筛选出符合条件的元素。由于filter()使用惰性计算,所以只有在取filter()结果的时候,
sorted
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但是如果是字符串或者两个dict呢?直接比较数字上的大小还是没有意义的,因此,比较的过程必须通过函数抽象出来。
sorted([36,5,-12,9,-21])
sorted([36,5,-12,9,21],key=abs)
默认情况下,对字符串排序,是按照ASCII的大小比较,大写字母Z会排在小写字母a的前面。
现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码的大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写比较两个字符串,实际上就是先把字符串都变成大写或者小写,再比较。
sorted([‘bob’,’about’,’Zoo’,’Credit’])
sorted([‘bob’,’about’,’Zoo’],key=str.lower)
sorted([‘bob’,‘about’,‘Zoo’],key=str.lower,reverse=True)
print(sorted(students,key=itemgetter(0)))
print(sorted(students,key=lambda t:t[1]))
print(sorted(students,key=itemgetter(1),reverse=True)
返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果返回。
def calc_sum(**args):
ax=0
for n in args
ax=ax+n
return ax
def lazy_sum(*args):
def sum():
ax=0
for n in args:
ax=ax+n
return ax
return sum
装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
def now():
print (‘2005-3-25’)
f=now
f()
函数对象有一个name属性,可以拿到函数的名字:
now.__name__
f.__name__
现在,假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期d动态增加的功能的方式,称之为装饰器。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wapper(*args,**kw):
print(‘call %s():’% func.__name__)
return func(*args,**kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
@log
def now():
print(’2015-3-25’)
调用now()函数,不仅会运行now()函数本身,还会运行now()函数前打印一行日志。
把@log放到now()函数的定义出,相当于执行了语句:
now=log(now)
由于log()是一个decorator,放回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量执行新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来更复杂。
def log(text):
def decorator(func):
def wrapper(*args,**kw):
print(‘%s %s():’%(text,func.__name__))
return func(*args,**kw)
return wrapper
return decorator
我们来解析上面的语句,首先执行log(‘execute’)
,放回的是decorator函数,在调用放回的函数,参数是now函数,返回值最终是wrapper函数。
以上两个钟decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的now
变成了wrapper
因为返回的那个wrapper函数名字就是wrapper,所以,需要把yu的函数的name等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args,**kw):
return func(*args,**kw)
return wrapper
偏函数
python的functools模块提供了很多有用的功能,其中一个就是偏函数。要注意,这里的偏函数和数学意义上的偏函数不一样。
在介绍函数参数的时候,我们讲到,通过设定参数的额默认值,可以降低函数。
int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换。
int(‘1234’,base=8)
当传入:
max2=functools,partial(max,10)
实际上会把10作为*args的一部分自动加到左边,也就是
max2(5,6,7)
相当于
args=(10,5,6,7)
max(*args)
functools.partial
就是帮助我们创建一个偏函数的,不需要我们定义int2(),可以直接使用下面的代码创建一个新的函数init2
import functools
int2 =functools.partial(int,base=2)
int2(‘10000’)
模块
使用模块
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在python中,是通过_前缀来实现的。
正常的函数和变量名是公开的,可以被直接引用。比如:abc,x123,PI等
类似xx这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的author,name就是特殊变量,hello模块定义的文档注释也可以用来特殊变量doc访问,我们自己的变量一般不要用这种变量名。
类似_xxx和__xx这样的函数或变量就是非公开的(private),不应该被直接引用.比如_abc,__abc等
之所以我们说,
安装第三方模块
模块搜索路径
当我们试图加载一个模块时,python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错:
默认情况下,python解释器会搜索当前目录、所有已安装的内置模块和第三方木模块,搜索路径存放在sys模块的path变量中
如果我们要添加自己的搜索路径,一是直接修改sys.path的路径。
面对对象
类和实例
面对对象最重要的是类和实例,必须牢记类时抽象的模板,比如student类,而实例是根据类创建出来的一个个具体的对象,每个对象都有着相同的方法,但各自的数据可能不同。
def student(object):
pass
class后面紧接的是类名,紧接着是(object)
,表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
后面的0x10a67a590是内存地址,每个object的地址都不一样,而student本身则是一个类。
可以自由的给一个实例变量绑定属性,比如,给bart绑定一个name的属性。
由于类可以起到模板的作用吗,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的init方法,在创建实例的时候,就把name,source
等属性绑上去:
class student(object):
def __init__(self,name,source):
self.name=name
self.source=source
注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向对象本身。
有了init方法的第一个参数永远是self,表示创建的实例本身,因此,在init的方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
在init方法,在创建实例的时候,就不能传入空的参数,比如传入与init方法匹配的参数,但self不需要传,python解释器自己会把实例变量传进去。
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通的函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
数据封装
面对对象编程的一个重要特点就是数据封装。在上面的student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
def print_score(std):
print (%s :%s %(std.name,std.score))
但是,既然studen实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在student类的内部定义数据的函数,这样,就把数据封装起来了。这些封装数据的函数和student类本身是关联起来的,我们称之为类的方法:
class student():
def __init__(self,name,score):
self.name=name
self.score=score
def print_score(self):
print (%s:%s %s(self.name,self.score))
要定义一个方法,除了第一个参数是self外,其它和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self
,其它参数正常传入:
bart.print_score()
这样一来,我们从外部看student类,就只需要制定name和score,而如何打印,都是在student类的内部定义的,这些数据和逻辑被封装起来了,调用很容易,但却不用制定内部实现的细节。
封装的另一个好处是可以给student类增加新的方法。
访问限制
在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就需要隐藏内部的复杂逻辑。
但是,从前面的student类的定义来看,外部代码还是可以自由修改一个实例的name、score属性:
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在python中,实例的变量名如果以开头,就变成了一个私有变量,只有内部可以访问,外部不能访问,所以,我们把student类改一改。
class student(object):
def __init__(self,name,score):
self._name=name
self._score=score
def print_score(self):
print (%s:%s %(self._name,self_score))
但是如果外部代码要获取name和score怎么办?可以给student类增加get_name
和get_score
这样的方法:
如果有要允许外部代码修改score怎么办?可以再给student类增加set_score方法:
class student(object):
def set_score(self,score):
self.__score=score
因为在方法中,可以对参数做检查,避免传入非法的参数
需要注意的是,在python中,变量名类似xxx,也就是双下划线开头,并以双下划线结尾的,是特殊变量,特殊变量是可以直接访问,不是private变量。
继承和多态
在oop程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类,而被继承的class称为基类、父类或超类。
对于dog来说,animal就是它的父类对于animal来说,dog就是它的子类,cat和dog类似。
继承有什么好处?最大的好处子类获得父类的全部功能。由于animal实现了run方法,因此,dog和cat作为它的子类,什么事也没有干,就自动拥有了run()方法。
继承的第二个好处需要我们对于代码进行一点点的改动,无论dog还是cat,它们的run的时候,都是现实animal is running,符合逻辑是的dog is running 和cat is running,因此,对于dog和cat分别进行了修改。
当子类和弗雷都存在相同的run()方法时,我们说,子类的run覆盖了父类的run(),在代码运行的时候,总是会调用子类的run().这样o我们就获得了继承的另一个好处:多态
要理解什么是多态,我们首先要对数据类型再作一点说明。我们顶一个class的时候,我们实际上就定义了一个数据类型。我们定义的数据类型和python自带的数据类型,比如str、list、dict没有什么两样。
判断一个变量是否某个类型可以用isintance()判断
dog可以看出animal,但animal不可以看出dog。
def run_twice(animal):
animal.run()
animal.run()
你会发现,新增一个animal的子类,不必对run_twice()做任何修改,实际上,任何依赖animal作为参数的函数或者方法都可以不加修改地正常运行,原因是因为多态。
这就是多态的真正的威力:调用方只管调用,不管细节,而当我们新增一个animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用。者就是著名的开闭
原则:
对扩展开发:允许新增animal子类:
对修改封闭:不需要修改因爱animal类型的run_twice()等函数
静态语音vs动态语音
对于静态语言来说,如果需要传入animal类型,则传入的对象必须是animal类型或者它的子类,否则,将无法调用run()方法。
对于python这样的动态语言来说,则不一定需要传入animal类型,我们只需要保证传入的对象有一个run()方法就可以了。
获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
使用type()
判断基本数据类型可以直接写int,str等,但如果要胖端一个对象是否是函数怎么办呢?可以使用types模块定义的常量:
import types
types.FunctionType
types.BuiltinFunctionType
types.LambdaType
types.GeneratorType
使用isinstance()
对于class的基础关系来说,使用type就很不方便。我们要判断class的类型,可以使用isinstance()函数。
isinstance(bar,Animal)
使用dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法。
dir(‘ABC’)
....
仅仅把属性和方法列出来是不够的,配合getattr(),setattr()以及hasattr(),我们可以直接操作一个对象的状态。
hasattr(obj,’x’)
obj.x
setattr(obj,’y’,19)
getattr(obj,’y’)
可以传入一个default参数,如果属性不存在,就放回默认值:
getattr(obj,’z’,404)
比较正规的写法如下:
def readImage(fp):
if hasattr(fp,’read’):
return readData(fp)
return None
实例属性和类属性
由于python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self变量:
class Student(object):
def __init__(self,name):
self.name=name
s=Student(‘Bob’)
s.score=90
但是,如果student类本身需要绑定一个属性呢?可以至二级在class中定义属性,这种属性,归student类所有:
class student(object):
name=’Student’
class Student(object):
name=“Student”
s=Student()
print(s.name) #因为实例并没有name属性,所以会继续查找class的name属性
从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
面对对象高级编程
使用slots
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,者就是动态语言的灵活性。先定义class:
def Student():
pass
s=Student()
s.name=‘Michael’
print (s.name)
还可以尝试给实例绑定一个方法:
def set_age(self,age):
self.age=age
from types import MethodType
s.set_age=MethodType(set_age,s)
s.set_age(25)
为了给所有实例都绑定方法,可以给class绑定方法
def set_score(self,score):
self.score=score
Student.set_score=set_score
通常情况下,上面的set_score方法可以直接在class中,但动态绑定允许我们在程序运行的中动态给class加上功能,这在静态语言中很难实现。
使用slots
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。
为了达到限制的目的,python允许在定义class的时候,定义一个特殊的slots变量,来限制class实例能添加的属性:
class Student(object):
__slots__=(‘name’,’age’)#用tuple定义允许绑定的属性名称
除非在子类中也定义slots。这样,子类实例允许定义的属性就是自身的slots加上父类的slots.
使用@property
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。
有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的python程序员来说,
对于类的方法,装饰器一样起作用。python内置的@property负责把一个方法编程属性调用的:
class Student(object):
@property
def score(self):
return self.score
@score.setter
def score(self,value):
if not isinstance(value,int):
raise ValueError(‘score must be a integer’)
if value <0 or value >100:
raise ValueError(‘score must between 0 ~ 100’)
self._score=value
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@propery本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有了一个可控的属性操作。
注意到这个神奇的@property,我们在堆实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法实现的。
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性。
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self,value):
self.birth=value
@property
def ages(self):
return 2015-self._birth
多重继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。
如果要在增加‘宠物类’和‘非宠物类’,这么搞下去,类的数量会呈指数增长,很明显这样设计是不行的。
正确的做法是采用多重继承。首先,主要的层次扔按照哺乳类和鸟类设计:
class Animal(object):
pass
class Mammal(Animal):
pass
class Bird(Animal):
pass
#各种动物
class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
现在,我们要给动物在加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类
class Runnable(object):
def run(self):
print(‘Running...’)
class Flyable(object):
def fly(self):
print(‘Flying...’)
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:
class Dog(Mammal,Runnable):
pass
对于需要Flybable功能的动物,就多继承一个Flyable,例如Bat:
class Bat(Mammal,Flyable):
pass
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
Mixln
在设计类的继承关系是,通常,主线都是单一继承下来的,例如,
Ostrich继承Bird。但是,如果需要混入额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承runnable。这种设计通常称之为Mixin。
Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。
python自带的很多库也使用了Mixin。举个例子,python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由forkingMixIn和ThreadingMixin提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程的模式的tcp服务,定义如下:
class MyTCPSever(TCPServer,ForkingMixIn):
pass
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer,ThreadingMixIn):
pass
如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixIn:
class MyTCPServer(TCPServer,Corout)
pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
定制类
看到类似slots这种形如xxx的变量或函数名就要注意,这些在python中是由特殊用途的。
slots我们已经知道怎么用了,除此之外,python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。
str
我们先定义个student类,打印一个实例:
class student(object):
def __init__(self,name):
self.name=name
怎么才能打印得好看呢?只需要定义好str方法,放回一个好看的字符串就可以了。
class Student(object):
def __init__(self,name):
self.name=name
def __str__(self):
return ‘Student object (name:%s)’ % self.name
这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。
但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看。
这是因为直接显示变量调用的不是str,而是repr返回用户看到的字符串,而repr返回程序开发者看到的字符串,也就是说,repr是为了调试服务器。
class Student(object):
def __init__(self,name):
self._name=name
def __str__(self):
return ’Student object (name:%s)’ % self._name
iter
如果一个类想被用于for… in循环,类似list或tuple那样,就必须实现一个iter方法,该方法返回一个迭代对象,然后,python的for循环就会不断调用该迭代对象的next()拿到循环的下一个值,直到stopIteration错误时推出循环。
class Fib(object):
def __init__(self):
self.a,self.b=0,1
def __iter__(self):
return self
def __next__(self):
self.a,self.b=self.b,self.a+self.b
if self.a >10000:
raise StopIteration()
return self.a
getitem
fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:
Fib()[5]
class Fib(object):
def __getitem__(self,n):
a,b=1,1
for x in range(n)
a,b=b,b+a
return a
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片 ** 也重要的
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
也没有对负数作处理,所以,要正确实现一个getitem还是有很多工作要做的。
此外,如果把对象看出dict,getitem的参数也可能是一个可以作key的object,例如str。
与之对应的setitem方法,把对象视作list或dict来对集合赋值。最后还有一个delitem方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和python自带的list、tuple、dict没有什么区别。
getattr
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。比如定义。
class Student(object):
def __init__(self):
self.name=‘Michael’
def __getattr__(self,attr):
if attr==‘score’:
return 99
当调用不存在的属性下,才调用getattr,已有的属性,比如name,不会再getattr中查找。此外,注意到任意调用如s.abc都会放回None,这是因为我们定义的getattr默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
call
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。能不能直接在实例本省上调用呢?在python中,答案是肯定的。任何类,只需要定义一个call方法,就可以直接对实例进行调用。
class Student(object):
def __init__(self,name):
self.name=name
def __call__(self):
print (‘My name is %s. ’% self.name)
s=Student(‘Michael’)
s()
call 还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看出函数,把函数看出对象,因此这两者之间本来及没啥根本的区别。
如果你把对象看出函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有call的类实例。
使用枚举类
当我们需要定义常量是,一个办法是用大写变量通过整数来定义,例如月份:
JAN=1
FEB=2
MAr=3
...
Nov=11
DEC=12
好处是简单,缺点是类型是int,并且仍然是变量。
更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。python提供了enum类来实现这个功能:
from enum import Enum
Month= Enum(‘Month’,(‘Jan’,‘Feb’,‘Mar’,‘Apr’,‘May’,‘Jun’,‘Jul’,‘Aug’,‘Sep’,‘Oct’,‘Nov’,‘Dec’))
这样我们就获得Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:
for name,member in Month.__members__.items():
print(name,’=>’,member,’,’,member.value)
value属性则是自动赋给成员的init常量,默认是从1开始计算。
如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:
from enum import Enum,unique
@unique
class Weekday(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
@unique 装饰器可以帮助我们检查包装没有重复值
day1=Weekday.Mon
print(day1)
Weekday.Mon
print(Weekday[‘Tue’])
Weekday.Tue
print(Weekday.Tue.value)
1
print(Weekday(1))
Weekday.Mon
可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
使用元类
type()
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
比方说我们要定义一个Hello的class,就写一个hello.py模块:
class Hello(object):
def hello(self,name=‘word’):
print(‘Hello,%s’ % name )
当python解释器载入hello模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个hello的class对象,测试如下
from hello import Hello
h=Hello()
h.hello()
type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型是type,而h是一个实例,它的类型就是class Hello。
我们说class的定义时运行时动态创建的,而创建class 的方法就是使用type()函数。
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)…定义
Hello=type(‘Hello’,(object,),dict(hello=fn)) #创建Hello class
要创建一个class对象,type()函数依次传入3个参数:
- class的名称
- 继承的弗雷集合,注意python支持多重继承,如果只有一个父类,别忘记tuple的单元素写法
- class 的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上
通过type()函数创建的类和直接写class是完全一样的,因为python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
metaclass
除了使用type()动态创建类之外,要控制类的创建行为,还可以使用metaclass
metaclass,直译为元类,简单的解释就是:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以,先定义metaclass,然后创建类。
连接起来就是:先定义metaclass,就可以创建类,最后创建实例。
所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成metaclass创建出来的‘实例’。
metaclass是python面向对象最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况。
class ListMetaclass(type):
def __new__(cls,name,bases,attrs):
attrs[‘add’]=lambda self,value:self.append(value)
return type.__new__(cls,name,bases,attrs)
有了ListMetaclass,我们在定义类的时候还要指示使用listMetaclass来定制类,闯入关键字metaclass:
class MyList(list,metaclass=ListMetaclass):
pass
当我们闯入关键字参数metaclass时,魔术就生效了,它指示python解释器在创建myList时,要通过ListMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
new()方法接收到的参数依次是:
1 当前准备创建的类的对象
2 类的名字
3 类继承的父类集合
4 类的方法集合
但是,总会遇到需要通过metaclass修改类定义的。orm就是一个典型的例子。
ORM全称‘Object Relation Mapping’,即对象-关系映射,也就是一个类对应一个表,这样,写代码更简单,不用直接操作sql语句。
要编写一个orm框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
让我们来尝试编写一个ORM框架。
编写底层模块的第一步,就是先把调用调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码:
class User(object):
id=IntegerField(‘id’)
name=StringField(‘username’)
email=StringField(‘email’)
passowrd=StringField(‘password’)
u=User(id=12345,name=‘Michael’,email=‘test@orm.org’,password=‘my-pwd’)
u.save()
其中,父类Model和属性类型stringField、IntegerField是由ORM框架提供的,剩下的魔术方法比如save()全部由metaclass自动完成。虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。
现在,我们就按上面的接口来实现该ORM。
首先来定义Field类,它负责保存数据库表的字段名和字段类型。
class Filed(object):
def __init__(self,name,column_type):
self._name=name
self._column_type=column_type
def __str__(self):
return ‘<%s:%s>’ %(self.__class__.__name__,self._name)
在Field的基础上,进一步定义各种类型的Field,比如StringField,IntegerFieldd等等
class StringField(Field):
def __init__(self,name):
super(StringFiled,self).__init__(name,’varchar(100)’)
class IntegerField(Field):
def __init__(self,name):
super(IntegerField,self).__init__(name,’bigint’)
下一步,就是编写最复杂的ModelMetaclass了:
class ModelMetaclass(type):
def __new__(cls,name,bases,attrs):
if name==‘Model’:
return type.__new__(cls,name,bases,attrs)
print(‘Found model:%s’% name)
mappings=dict()
for k,v in attrs.items():
if isintance(v,Field):
print(‘Found mapping:%s ==>’ %(k,v)) mappings[k]=v
for k in mappings,keys():
attrs.pop(k)
attrs[‘__mappings__’]=mappings
attrs[‘__table__’]=name
return type.__new__(cls,name,bases,attrs)
以及基类Model:
class Model(dict,metaclass=ModelMetaclass):
def __init__(self,**kw):
super(Model,self).__init__(**kw)
def __getattr__(self,key):
try:
return self[key]
except KeyError:
raise AttributeError(r”’Model’object has no attribute ‘%s’” % key)
def __setattr__(self,key,value):
self[key]=value
def save(self):
fields=[]
params=[]
args=[]
for k,v in self.__mappings__items():
fields.append(v.name)
params.append(‘?’)
args.append(getattr(self,k,None))
sql=‘insert into %s(%s) values (%s )’ % (self.__table__,’,’.join(fields),’,’.join(params))
print(‘SQL:%s’ % sql)
print(‘ARGS:%s’ % str(args))
当用户定义一个class User(Model)时,Python解析器首先在当前类User的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclass的ModelMetaclass来创建User类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。
在ModelMetaclass中,一共做了几件事情:
- 排除掉队Model类的修改
- 在当前类中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个mappings的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误
- 把表名保存在table中,这里简化为表名默认为类名
在model类中,就可以定义各种操作数据库的方法。
我们事先了save() 方法,把一个实例保存到数据库中,因为有表名,属性到字段的映射和属性值的集合,就可以构造出insert语句。