python基础-对象高级特性

一、实例绑定:

二、使用__slots__:

三、@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:动态创建的好处

  4、类属性和实例属性:区别

-----------------------------------------------------------------------------------------------------------------

一、实例绑定:

  动态的给一个实例绑定属性和方法:

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

二、使用__slots__:

  现在有一个需求:限制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的目的就是给一个类增加多个功能,这样,在设计类的时候,优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。

  比如,编写一个多进程模式的TCP服务,定义如下:

class MyTCPServer(TCPServer, ForkingMixin):
    pass

  编写一个多线程模式的UDP服务,定义如下:

class MyUDPServer(UDPServer, ThreadingMixin):
    pass

  不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。

五、定制类

  形如__xxx__的变量或者函数名,在Python中有特殊用途,比如:__slots__,__len__()方法作用于len()函数。

  1、 __str__ 和 __repr__ 打印一个实例

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

  2、 __iter__ 被用于 for ... in循环

     实现__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__()方法,用于删除某个元素.

  4、__getattr__ : 动态返回一个属性

    当调用不存在的属性时,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

  5、__call__ : 对实例本身进行调用

    任何类,只需要定义一个__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

  3、metaclass动态创建ORM:

    动态修改有什么意义?直接在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和属性类型StringFieldIntegerField是由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,比如StringFieldIntegerField等等:

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]

    详情参考:这里

  4、类属性和实例属性:

    直接在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类的所有类属性,目的就是避免造成混淆。

 

posted @ 2015-10-28 23:35  超超xc  Views(653)  Comments(0Edit  收藏  举报
I suppose,were childrenonec.