python基础-对象高级特性
三、@property:python内置装饰器 decorator
四、多重继承:Mixin
1、 __str__ 和 __repr__: 打印一个实例
2、 __iter__: 被用于 for ... in
循环
3、__getitem__ :现得像list那样按照下标取出元素,需要实现__getitem__()
方法
4、__getattr__ : 动态返回一个属性
5、__call__ : 对实例本身进行调用
六、使用元类: metaclass
1、type(): 通过type()
函数创建类,而无需通过class
2、metaclass: 除了使用type()
动态创建类以外,要控制类的创建行为,还可以使用metaclass。--元类.
3、metaclass动态创建ORM:动态创建的好处
-----------------------------------------------------------------------------------------------------------------
动态的给一个实例绑定属性和方法:
>>> class Student(object): ... pass ... >>> s = Student() >>> s.name='Micheal' #动态给实例绑定一个属性 >>> print s.name Micheal >>> def set_age(self,age): #定义一个函数作为实例方法 ... self.age = age ... >>> from types import MethodType >>> s.set_age = MethodType(set_age,s,Student) #给实例绑定一个方法 >>> s.set_age(25) # 调用实例方法 >>> s.age #测试结果 25
注意:给一个实例绑定的方法,对另一个实例是不起作用的:
>>> s2 = Student() # 创建新的实例 >>> s2.set_age(25) # 尝试调用方法 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'set_age'
给class绑定的方法,所有实例可用:
>>> def set_score(self, score): ... self.score = score ... >>> Student.set_score = MethodType(set_score, None, Student)
>> s.set_score(100) >>> s.score 100 >>> s2.set_score(99) >>> s2.score 99
现在有一个需求:限制class的属性(包括变量和函数),那么在类中可以定义 __slots__:
>>> class Student(object): ... __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称 ... >>> s = Student() # 创建新的实例 >>> s.name = 'Michael' # 绑定属性'name' >>> s.age = 25 # 绑定属性'age' >>> s.score = 99 # 绑定属性'score' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
注意:__slots__
定义的属性仅对当前类起作用,对继承的子类是不起作用的:
>>> class GraduateStudent(Student): ... pass ... >>> g = GraduateStudent() >>> g.score = 9999
除非在子类中也定义__slots__
,这样,子类允许定义的属性就是自身的__slots__
加上父类的__slots__
。
三、@property,python内置装饰器 decorator
既能检查参数,又可以用类似属性这样简单的方式来访问类的变量:
class Student(object): @property def score(self): #相当于getter,,,property会自动生成一个socre.setter return self._score @score.setter def score(self, value): #相当于setter,,,弱想只读属性,即不声明次函数和decorator即可. 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
>>> s = Student() >>> s.score = 60 # OK,实际转化为s.set_score(60) >>> s.score # OK,实际转化为s.get_score() 60 >>> s.score = 9999 Traceback (most recent call last): ... ValueError: score must between 0 ~ 100!
Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixin): pass
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixin): pass
不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
形如__xxx__
的变量或者函数名,在Python中有特殊用途,比如:__slots__,__len__()
方法作用于len()
函数。
>>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ... __repr__ = __str__ #按变量打印也如 __str__ 函数 ... >>> print Student('Michael') #这里调用 __str__ Student object (name: Michael) >>> s = Student('Michael') >>> s #这里调用 __repr__ Student object (name: Michael)
实现__iter__()方法,for循环就会不断调用该迭代对象的next()
方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
例如:斐波那契数, 除了第一个和第二个,后一个数都是前两个数想加的结果.
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def next(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100000: # 退出循环的条件 raise StopIteration(); return self.a # 返回下一个值
>>> for n in Fib(): ... print n ... 1 1 2 3 5 ... 46368 75025
3、__getitem__ :现得像list那样按照下标取出元素,需要实现__getitem__()
方法
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a
>>> f = Fib() >>> f[0] 1 >>> f[1] 1 >>> f[2] 2 >>> f[3] 3 >>> f[10] 89 >>> f[100] 573147844013817084101
list中切片实现方法:f[a:b], 切片对象为slice.
>>> class Fib(object): ... def __getitem__(self,n): ... if isinstance(n,int): ... a,b=1,1 ... for x in range(n): ... a,b = b, a+b ... return a ... if isinstance(n,slice): ... start = n.start ... stop = n.stop ... a,b = 1,1 ... L = [] ... for x in range(stop): ... if x >= start: ... L.append(a) ... a,b=b,a+b ... return L ... >>> f = Fib() >>> f[0:5] [1, 1, 2, 3, 5] >>> Fib()[2:7] [2, 3, 5, 8, 13]
以上还没有对负数,step做处理,如果把对象看成dict对象,还应该处理传入key的情况,key的类型为str.
与之对应的还有 __setitem__(),把对象视作list或dict来对集合赋值,__delitem__()方法,用于删除某个元素.
当调用不存在的属性时,Python解释器会试图调用__getattr__(self, 'score')
来尝试获得属性,这样,就有机会返回score
的值.
>>> class Student(object): ... def __init__(self): ... self.name='Michael' ... def __getattr__(self,attr): #attr 为临时变量,调用时为存存储了str类型的属性名字. ... if attr=='score': ... return 99 ... >>> s = Student()>>> s.score 99
返回函数也可以:
>>> class Teacher(): ... def __getattr__(self,attr): ... if attr == 'age': ... return lambda:25 ... >>> t = Teacher() >>> t.age #该属性为一个匿名方法. <function <lambda> at 0x0059D0F0> >>> t.age() 25
注意:在没有找到属性的情况下,才调用__getattr__(selt,attr) ,定义该属性后,其他属性都会返回None,那么按照约定应该返回AttributeError.
class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。
例子:
现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似:
http://api.server/user/friends
http://api.server/user/timeline/list
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。
利用完全动态的__getattr__
,我们可以写出一个链式调用:
>>> class Chain(object): ... def __init__(self,path=''): ... self._path = path ... def __getattr__(self,path): ... return Chain('%s/%s' %(self._path,path)) ... def __str__(self): ... return self._path ... __repr__ = __str__ ... >>> Chain().status.user.timeline.list /status/user/timeline/list >>> Chain().s.as.sd #发现在这种方式中属性不能为python中的关键字.是个坑喔.这个坑可以解决,用下面问题的实现就可解决. File "<stdin>", line 1 Chain().s.as.sd ^ SyntaxError: invalid syntax >>> Chain().s /s >>> Chain().s.a /s/a >>> Chain().s.ads.a /s/ads/a
还有些REST API会把参数放到URL中,比如GitHub的API:该题链接.
GET /users/:user/repos #需要把:user
替换为实际用户名
写出这样的链式调用:
Chain().users('michael').repos
我的答案:
class Chain(object): def __init__(self, path=''): self._path = path def __getattr__(self, path): return Chain('%s/%s' % (self._path, path)) def __str__(self): return self._path def __getitem__(self, path): #支持下标Chain().users['Michael'].repos return Chain('%s/%s' % (self._path, path)) def __call__(self,name): return Chain('%s/%s' % (self._path, name)) # def users(self,name='default'): #仅针对Chain().users('Michael').repos起作用,不够灵活. # return Chain('/users/'+name) __repr__ = __str__ >>> Chain().users('michael').repos GET /users/michael/repos >>> Chain().a.b.c GET /a/b/c >>>Chain().users['michael'].repos /users/michael/repos >>>Chain().users('as').repos #关键字也不怕 /users/as/repos
任何类,只需要定义一个__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() My name is Michael:
判断一个对象是否能被调用,能被调用的对象就是一个Callable
对象,比如函数和我们上面定义的带有__call()__
的类实例
>>> s= Student('Michael') >>> s() My name is Michael. >>> callable(s) True >>> callable(filter) #filter为内置函数呢,居然是callable对象. True >>> callable(int) True >>> callable(str) True
1、type(): 通过type()
函数创建类,而无需通过class
定义:
>>> def fn(self,name='world'): ... print('Hello,%s.' % name) ... >>> Hello = type('Hello',(object,),dict(hello=fn)) #使用type动态创建出类. >>> h = Hello() >>> h.hello() Hello,world. >>> print (type(Hello)) <type 'type'> >>> print(type(h)) <class '__main__.Hello'>
创建一个class对象,type()
函数依次传入3个参数:
1.class的名称;--str
2.继承的父类集合, --tuple
3.class的方法名称与函数绑定 --dict , {name = func}
通过type()
函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()
函数创建出class
2、metaclass: 除了使用type()
动态创建类以外,要控制类的创建行为,还可以使用metaclass。--元类.
先定义metaclass,就可以创建类,最后创建实例。
metaclass允许你创建类或者修改类的定义.可以把类看成是metaclass创建出来的“实例”
metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass
一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add
方法:
# metaclass是创建类,所以必须从`type`类型派生: >>> class ListMetaclass(type): ... def __new__(cls,name,bases,attrs): ... attrs['__slots__'] = ('ad','add') #限制创建类的属性 ... attrs['add'] = lambda self,value:self.append(value) #继承自list,所以有append方法. ... attrs['address'] = 2 #该属性为read-only,,因为attrs['__slots__']中的tuple没有定义. ... return type.__new__(cls,name,bases,attrs) #使用type来创建类. ... >>> class MyList(list): #继承自list ... __metaclass__ = ListMetaclass # 指示使用ListMetaclass来 创建 类 达到 定制 的 效果 ... >>> L = MyList() >>> L.add(1) >>> L [1] >>>L.ad = 2 >>>L.ad 2 >>>L.addss = 2 AttributeError: 'MyList' object has no attribute 'addss' >>>L.address = 3 AttributeError: 'MyList' object attribute 'address' is read-only
__metaclass__ = ListMetaclass
语句,指示Python解释器在创建MyList
时,要通过ListMetaclass.__new__()
来创建,在此,我们可以修改类的定义,父类继承,比如,加上新的方法,然后,返回修改后的定义。
可能有的同学对__init__和__new__感到困惑: 参考
Use __new__ when you need to control the creation of a new instance. Use __init__ when you need to control initialization of a new instance.
__new__()
方法接收到的参数依次是:
1.当前准备创建的类的对象:--typeType
2.类的名字:--str
3.类继承的父类集合:--tuple
4.类的属性集合:--dict
动态修改有什么意义?直接在MyList
定义中写上add()
方法不是更简单吗?正常情况下,确实应该直接写,通过metaclass修改纯属变态。
但是,总会遇到需要通过metaclass修改类定义的。ORM就是一个典型的例子。
ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。
要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
尝试编写一个ORM框架。
编写底层模块的第一步,就是先把调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User
类来操作对应的数据库表User
,期待写出这样的代码:
class User(Model): # 定义类的属性到列的映射: id = IntegerField('id') name = StringField('username') email = StringField('email') password = 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 Field(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
,IntegerField
等等:
class StringField(Field): def __init__(self, name): super(StringField, 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) mapping = dict() #在类中增加一个dict类型的映射 mapping for k,v in attrs.iteritems(): #遍历内嵌的属性,这里的属性值为另两种类型(StringField 和 IntergerField) if isinstance(v,Field): print('映射:%s==>%s' %(k,v) ) mapping[k] = v print'属性名:%s,列名:%s,类型:%s' %(k,v.name,v.column_type) for k in mapping.iterkeys(): attrs.pop(k) #删除原有类属性名,避免混淆,统一存在mapping映射中. attrs['__table__'] = name #假设表名和类名一致. attrs['__mapping__'] = mapping #保存属性和列以及类型的映射关系. return type.__new__(cls,name,bases,attrs)
以及基类Model
:
#实体类基础 class Model(dict): __metaclass__ = ModelMetaclass def __init__(self,**kw): super(Model,self).__init__(**kw) #这里利用dict的__init__()方法. def __getattr__(self,key): try: return self[key] except KeyError: raise AttributeError(r"'Model' ojbect has no attribute '%s'" % key) def __setattr__(self,key,value): self[key] = value def save(self): fields = [] params = [] args = [] for k,v in self.__mapping__.iteritems(): #mapping为一个dict.并且在metaclass中定义. fields.append(v.name) #添加定义的列名 #获取创建实例时根据列名保存的参数. parame = getattr(self,k,None) if parame is None: #parame不能为None,''.join()不支持None raise TypeError('parame %s NoneType Found' % k) if isinstance(parame,int): #判断获取的值是否是int,转化为str,否则下面的join函数int类型会解析错误. params.append(str(parame)) else: params.append(parame) sql = 'insert into %s (%s) values (%s)' % (self.__table__,','.join(fields),','.join(params)) print('SQL:%s' % sql)
当用户定义一个class User(Model)
时,Python解释器首先在当前类User
的定义中查找__metaclass__
,如果没有找到,就继续在父类Model
中查找__metaclass__
,找到了,就使用Model
中定义的__metaclass__
的ModelMetaclass
来创建User
类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。
下面我们定义上面的User类继承自Model
class User(Model): #定义类的属性到列的映射, id = IntergerField('ID') name = StringField('USERNAME') email = StringField('EMAIL') password =StringField('PASSWD')
测试:
u = User(id=12345,name='Michael',email='test@orm.org',password='my-pwd') #根据类中定义的列,创建实例. u.save()
映射:email==><StringField:EMAIL> 属性名:email,列名:EMAIL,类型:varchar(100) 映射:password==><StringField:PASSWD> 属性名:password,列名:PASSWD,类型:varchar(100) 映射:id==><IntergerField:ID> 属性名:id,列名:ID,类型:bigint 映射:name==><StringField:USERNAME> 属性名:name,列名:USERNAME,类型:varchar(100) SQL:insert into User (PASSWD,EMAIL,USERNAME,ID) values (my-pwd,test@orm.org,Michael,12345) [Finished in 0.1s]
详情参考:这里
直接在class中定义的是类属性:
class Student(object): name = 'Student'
实例属性必须通过实例来绑定,比如self.name = 'xxx'
。测试一下:
>>> # 创建实例s: >>> s = Student() >>> # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性: >>> print(s.name) Student >>> # 这和调用Student.name是一样的: >>> print(Student.name) Student >>> # 给实例绑定name属性: >>> s.name = 'Michael' >>> # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性: >>> print(s.name) Michael >>> # 但是类属性并未消失,用Student.name仍然可以访问: >>> print(Student.name) Student >>> # 如果删除实例的name属性: >>> del s.name >>> # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了: >>> print(s.name) Student
因此,在编写程序的时候,千万不要把实例属性和类属性使用相同的名字。
在上面编写的ORM中,ModelMetaclass
删除掉了User类的所有类属性,目的就是避免造成混淆。