面向对象(二)
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:
from types import MethodType
class Student(object):
pass
s = Student()
s.name = 'Michael' # 动态给实例绑定一个属性
print(s.name)
def set_age(self, age): # 定义一个函数作为实例方法
self.age = age
s.set_age = MethodType(set_age, s) # 给实例绑定一个方法(创建一个link指向外部的方法)
s.set_age(22) # 调用实例方法
print(s.age)
但是要注意是的给一个实例绑定方法和属性,对另一个是不起作用的。如果要给所有实例都绑定方法,可以给class绑定方法:
class Student(object):
pass
s = Student()
def set_score(self, score):
self.score = score
# Student.set_score = MethodType(set_score, Student)
Student.set_score = set_score
s2 = Student()
s.set_score(80)
s2.set_score(80)
print(s.score)
print(s2.score)
使用__slots__
如果我们想要限制实例的属性,我们可以定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
ss = Student()
ss.name = 'ben'
ss.score = 90
ss.age = 22 # AttributeError: 'Student' object has no attribute 'age'
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
class PostgraduateStudent(Student):
pass
g = PostgraduateStudent()
g.score = 9999
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
class Student(object):
__slots__ = ['name', 'score']
class PostgraduateStudent(Student):
__slots__ = ['age']
ps = PostgraduateStudent()
ps.age = 22
ps.score = 90
print(ps.score)
多重继承
上一节我们其实也讲了一点继承的概念。继承是面向对象编程的一个重要方式,子类可以通过继承扩展父类的功能。
举一个例子:Dog狗、Bat蝙蝠、Parrot鹦鹉、Ostrich鸵鸟。
如果安装哺乳动物和鸟类归类,我们可以设计出这样的类的层次: 
但是如果按照“能跑”和“能飞”来归类,我们就应该设计出这样的类的层次: 
如果要把上面的两种分类都包含进来,我们就得设计更多的层次:哺乳类:能跑的哺乳类,能飞的哺乳类;鸟类:能跑的鸟类,能飞的鸟类。这么一来,类的层次就复杂了: 
如果要再增加“宠物类”和“非宠物类”,这么搞下去,类的数量会呈指数增长,很明显这样设计是不行的。
正确的做法是采用多重继承。首先,主要的类层次仍按照哺乳类和鸟类设计:
class Animal(object):
pass
# 大类:
class Mammal(Animal):
pass
class Bird(Animal):
pass
# 各种动物:
class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
现在,我们要给动物再加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类:
class Runnable(object):
def run(self):
print('Running...')
class Flyable(object):
def fly(self):
print('Flying...')
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog;对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat;
class Dog(Mammal, Runnable):
pass
class Bat(Mammal, Flyable):
pass
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。
为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
定制类
上一节我们讲到python有很多类专有方法,其实这些方法都是有特殊用途的,可以帮助我们定制类。
__str__
__str__方法用于定义返回的字符串
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
print(Student('Yisa'))
__iter__
如果一个类想和list或tuple那样被用于for … in循环,就必须实现一个__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 # 返回下一个值
for i in Fib():
print(i)
__getitem__
Fib实例虽然能作用于for循环,看起来和list有点像,但是把它当成list来使用还是不行,比如我们无法通过下标索引。如果要表现得像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
print(Fib[0])
如果我们还想实现list的切片特性,可以这样:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
print(f[:10])
枚举类
python为枚举类型定义一个class类型,然后每个常量都是class的一个唯一实例。比如月份1-12。我们可以这样定义:
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 # @unique装饰器可以帮助我们检查保证没有重复值
class Weekday(Enum):
Sun = '00' # Sun的value被设定为0
Mon = '11'
Tue = '22'
Wed = '33'
Thu = '44'
Fri = '55'
Sat = '66'
for name, member in Weekday.__members__.items():
print(name, '=>', member, ',', member.value)