您的位置:首页 > 编程语言 > Python开发

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

2017-08-15 20:20 411 查看

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)
f32d
:
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='12133@Weishizhu.com',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中非常具有魔术性的对象,它可以改变类创建时的行为,这种强大的功能使用起来务必小心。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python