廖雪峰Python教程阅读笔记——7. 面向对象高级编程

7 面向对象高级编程

数据封装、继承和多态只是面向对象程序设计中最基础的3个概念,还有其他的高级特性,比如:多重继承、定制类、元类等概念

7.1 使用slots

正常情况下,当我们定义了一个class,创建了一个class实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性,比如:

class Student(object):
    pass

然后,尝试给实例绑定一个属性:

>>>s = Student()
>>>s.name = 'Mical'
>>>print(s.name)
Mical

还可以尝试给实例绑定一个方法:

>>>def set_age(self,age):
...     self.age = age
...
>>>from types import MethodType
>>>s.set_age = MethodType(set_age,s) #给实例绑定一个方法
>>>s.set_age(25) #尝试调用该方法
>>>s.age
25

但是,给一个实例绑定方法,对另外一个实例并不起作用。为了解决,可以给class类直接绑定方法:

>>>def set_score(self,score):
...     self.score = score
...
>>>Student.set_score = set_score #给类绑定一个方法

这样,该类Student 就可以使用该方法。通常情况下,上面的set_score()方法可以定义在类中。

但是,如果我们想要限制实例的属性,比如只能对Student类添加name和age属性。我们可以使用在定义class时,定义一个特殊的__slots__变量,来限制class实例能添加的属性:

class Student(object):
  __solts__ = ('name','age') #用tuple定义允许绑定的属性名称

注意,__slots__定义的属性只针对当前类起作用,对继承该类的子类不起作用。

7.2使用@property

@property是一个装饰器,负责把一个方法变成属性调用:

class Student(object):
    @property #把一个getter变成属性,只需要加上@property
    def score(self):
        return self._score

    @score.setter   #@property又创建了一个@score.setter 把setter方法编程属性
    def score(self,value):
        if not isinstance(value,int):
            raise ValueError('score must be an integer')
        if value < 0 and value > 100:
            raise ValueError('score must be between 0~100')

        self._score = value

@property的实现比较复杂,我们在对实例操作的时候,就知道该属性很肯能不是直接暴露的,而是通过getter和setter方法来实现的。

小结:
@property广泛应用在类的定义中,可以让调用者写出间断的代码,同时保证对参数进行必要的检查。

7.3 多重继承

对于子类C,既可以继承父类A,又可以继承父类B:

class C(A,B):
    pass

通过多重继承关系,一个子类可以获得多个父类的多个方法。

  • MixIn
    多重继承关系,比如上述子类C,既可以继承父类A,又可以继承父类B,这种行为成为MixIn
    无需类似于java如果要继承,有强大的继承链。Python只需要选择组合不同的类功能,就可以快速构造出所需的子类。

小结:
Python允许使用多重继承,因此,MixIn是一种常见的涉及。
只允许单一继承的语言-Java,不能使用MixIn涉及

7.4 定制类

看到类似__slots__这种形如__XXX__的变量或者函数时,这在Python中是有特殊用途的。
- str

 class Student(object):
    def __init__(self,name):
        self.name = name

我们打印一个实例:

>>>print(Student("Mical"))
<__main__.Student object at 0x109afb190>

我们看到,打印的是一个对象的地址信息,如何能打印我们能看懂的字符串呢?
只需要定义一个__str__()方法,返回一个好看的字符串就行了:

class Student(object):
    def __init__(self,name):
        self.name = name
    def __str__(self):
        return 'Student object (name :%s) % self.name'

下面看两种调用方式:

1.打印:

>>>print(Student('Hello'))
Student object (name :Hello)
>>>s = Student('Hello')
>>>s
<__main__.Student object at 0x109afb190>

注意到两者显示的效果不一样,这是因为直接显示变量s,并不是调用的__str()__而是调用的__repr()__,两者的区别是__str()__返回的是用户看到的字符串,__repr()__返回的是程序开发者看到的,用于程序调试

  • __iter__
    如果一个类想被用于for…in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个对象,然后Python的for循环就会不断的调用该迭代对象的__next__()方法,拿到下一个元素的值,直到遇到StopIteration异常才会停止。
    我们编写一个类,使其可以for循环,比如斐波那契数列函数:
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>100000: #退出循环
            raise StopIteration
        return self.a #返回下一个值

现在,试图遍历Fib()类的实例

>>>for n in Fib():
...   print(n)
...
1
1
2
3
5
  • getitem
    Fib实例虽然能作用于for循环,但是,把它当list来使用,还是不行,比如获取第index个元素:
>>>Fib()[5]

会提示Fib对象并不支持indexing
对Fib类进行改造:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 0
    for x in range(n):
        a, b = a,a+ b
    return a

现在就可以使用下标来访问Fib()实例对象了

7.5 使用枚举类

Python提供了Enum类来实现每个变量都是class的一个实例:

>>>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属性则是自动赋值给成员int常量,默认从1开始。
如果需要更精确的控制枚举类型,可以从Enum派生出自定义的类:

from enum import Enum,unique
@unique
Class Weekday(Enum):
    Sun=0 #Sun的value被设定为0
    Mon=1
    Tue=2
    Wed=3
    Thu=4
    Fri=5
    Sat=6

@unique装饰器可以检查枚举类型中有无重复元素
访问这些枚举类型可以有一下几种方式:

>>>Weekday.Sun
>>>Weekday['Sun']
>>>Weekday(1)
>>>Weekday.Sun.value

7.6使用元类

  • type()
    动态语言和静态语言不同,就是函数和类的定义,不是编译时定义的,而是在运行时动态创建的。
    比如定义一个Class Hello()类,当Python载入Hello模块时,就会依次执行该模块中的所有语句,执行结果就是动态创建出一个Hello的class对象。
>>>hello = Hello()
>>>print(type(Hello))
<class 'type'>
>>>print(type(hello))
<class hello.Hello>

type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而hello是一个实例,它的类型就是class Hello
type()函数不仅可以查看类型,也可以创建一个类:

>>>#先定义函数
>>>def fn(self,name='world'):
    print('hello, %s.' % name )
>>>Hello = type('Hello',(object,),dict(hello=fn))#创建Hello class

要创建一个class对象,type()函数一次传入三个参数:
1. class的名称
2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple单元素的写法:用”,”号
3. class方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上
通过type()函数创建的类和直接写class是完全一样的。

  • metaclass
    除了使用type()动态创建类外,要控制类的创建行为,还可以使用metaclass
    metaclass,称为元类。
    当我们定义了类以后,就可以根据这个类创建出示例,所以顺序为:先定义类,再创建实例。如果我们要创建类,必须先定义metaclass
    metaclass允许创建类和修改类,也可以认为,类是metaclass创建出的“实例”

例如:给我们自定义的类MyList增加一个add方法:
定义ListMetaclass ,按照默认习惯,metaclass 的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:

#metaclass 是类的模板,所以必须从'type'类型派生:
class ListMetaclass(type):
    def __new__(cls,name,bases,attrs):
        attrs['add'] = lambda self,value: self.append(value)
        retrun type.__new__(cls,name,bases,attrs)

有了MetaClass类这个局部模板,我们可以定义类时,指示使用ListMetaclass元类来定制这个类:

class MyList(list,metaclass=ListMetaclass):
    pass

当我们传入参数metaclass=ListMetaclass时,它指示Python解释器在创建 Mylist 时,要通过ListMetaclass._new__()来创建,在此,我们可以修改类的定义,比如加上新方法,然后返回修改后的定义。
__new__()方法接收的参数依次是:
1. 当前准备创建的类的对象 #cls
2. 类的名字 #name
3. 类继承的父类集合 #bases
4. 类的方法集合 #attrs
通常情况下,如果要定义某一个方法,在类定义时,就已经确定,但是也有另外情况——ORM.
ORM称为”Object Relation Mapping”,对象——关系映射,要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
举例写一个ORM框架:
1. 编写底层模块的第一步,就是先把调用接口写出来,比如使用者使用这个ORM框架,想定义一个User类来操作对应的数据库表user.我们期待他写出的代码是:

class User(Model):
    #定义类的属性到列的映射
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

创建一个实例

u = User(id=12345,name='Weishzhu',email='[email protected]',password='weishzhu')
#保存到数据库
u.save()

其中,父类Model和域类型IntegerField|StringField都是由ORM提供的。剩下的方法,比如save()全部由metaclass自动完成。虽然metaclass的编写会比较复杂,但orm的使用者用起来会非常简单
现在我们按照上面的接口来实现该ORM
首先定义Filed类,它负责保存数据库表字段名和字段类型:

class Field(object):
    def __init__(self,name,column_type):
        self.name = name
        self.column_type

    def __str__(self):
  return '<%s,%s>' % self.__class__.name,self.name

在Field的基础上,进一步定义各种类型的Field,比如IntegerField|StringField

class IntegerField(Filed):
    def __init__(self,name):
        super(IntegerField,self).__init__(name,'bigint')

class StringField(Field):
    def __init__(self,name):
        super(StringField,self).__init__(name,'varchar(100)')

下面就是编写复杂的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 isinstance(v,Field):
                print('Found mapping :%s ==> %s' % (k,v)))
                mapping[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' % attr(args))

当用户定义一个class User(Model)时,Python解释器首先在当前类User的定义中查找metaclass ,如果没有找到,则在父类Model中找metaclass。找到了就用Model中定义的metaclass的ModelMetaclass来创建一个User类,也就是说metaclass可以隐式地继承到子类。但子类自己感觉不到。
在ModelMetaclass中,要做以下几步:
1. 排除掉队Model类的修改;
2. 在当前类中,比如User中查找定义的类的所有属性,如果找到一个Filed属性,就把它保存到一个mappings的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误。
3. 把表名保存到__table__中,这里简化为表名默认为类名。

在Model类中,就可以定义各种操作数据库的方法,save()update()

小结:
metaclss是Python中非常具有魔术性的对象,它可以改变类创建时的行为,这种强大的功能使用起来务必小心。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章