python 04 面向对象高级编程

使用slots

s = Student()

s.name = 'Michael' # 动态给实例绑定一个属性

print s.name

Michael

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

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 "", line 1, in

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)

使用slots

但是,如果我们想要限制class的属性怎么办?比如,只允许对Student实例添加name和age属性。

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class能添加的属性:

class Student(object):

... slots = ('name', 'age') # 用tuple定义允许绑定的属性名称

还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:

Mixin

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为Mixin。

为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixin和FlyableMixin。类似的,你还可以定义出肉食动物CarnivorousMixin和植食动物HerbivoresMixin,让某个动物同时拥有好几个Mixin:

class Dog(Mammal, RunnableMixin, CarnivorousMixin):

pass

Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。

Python自带的很多库也使用了Mixin。举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixin和ThreadingMixin提供。通过组合,我们就可以创造出合适的服务来。

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

定制类

这是因为直接显示变量调用的不是str(),而是repr(),两者的区别是str()返回用户看到的字符串,而repr()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。

解决办法是再定义一个repr()。但是通常str()和repr()代码都是一样的,所以,有个偷懒的写法:

class Student(object):

def __init__(self, name):

    [self.name](http://self.name) = name

def __str__(self):

    return 'Student object (name=%s)' % [self.name](http://self.name)

__repr__ = __str__

iter

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

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 # 返回下一个值

getitem

Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:

Fib()[5]

Traceback (most recent call last):

File "", line 1, in

TypeError: 'Fib' object does not support indexing

要表现得像list那样按照下标取出元素,需要实现getitem()方法:

call

一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。能不能直接在实例本身上调用呢?类似instance()?在Python中,答案是肯定的。

任何类,只需要定义一个call()方法,就可以直接对实例进行调用。请看示例:

class Student(object):

def __init__(self, name):

    [self.name](http://self.name) = name

def __call__(self):

    print('My name is %s.' % [self.name](http://self.name))

使用枚举类

更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

使用元类

metaclass

除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到。

我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:

定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:

断言

凡是用print()来辅助查看的地方,都可以用断言(assert)来替代:

def foo(s):

n = int(s)

assert n != 0, 'n is zero!'

return 10 / n

def main():

foo('0')

assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。

如果断言失败,assert语句本身就会抛出AssertionError:

$ python3 err.py

Traceback (most recent call last):

...

AssertionError: n is zero!

程序中如果到处充斥着assert,和print()相比也好不到哪去。不过,启动Python解释器时可以用-O参数来关闭assert:

你可能感兴趣的:(python 04 面向对象高级编程)