数据封装、继承和多态是面向对象程序设计中最基础的3个概念,今天学习下Python中的高级特性——多重继承、定制类、元类等。
1、使用 __slots__
- 类的实例对象被创建后,我们可以给该实例绑定新的任何属性或方法。
- 但新绑定的属性和方法仅对当前实例有效,对该类的其它实例无效,要想也生效,我们需要将其绑定在该类自身上。
class Student(object):
pass
# 给实例绑定新的属性和方法
s1 = Student()
s1.name = 'hello'
from types import MethodType
def set_age(self, age):
self.age = age
s1.set_age = MethodType(set_age, s1)
# 测试一下
print(s1.name) # 'hello'
s1.set_age(30)
print(s1.age) # 30
s2 = Student()
print(s2.name) # 报错'Student' object has no attribute 'name'
# 给类绑定新的属性和方法
Student.grade = '一年级'
def set_score(self, score):
self.score = score
Student.set_score = set_score
# 测试一下
print(s2.grade) # '一年级'
s2.set_score(100)
print(s2.score) # 100
- 在定义class类的时候,使用
__slots__
这个特殊的变量,可以限制该class实例能添加哪些属性,如下例子中的Student实例只能追加 name 和 age 属性,追加其它属性会报错。
class Student(object):
# 用tuple定义允许绑定的属性名称
__slots__ = ('name', 'age')
s = Student()
s.name = 'geekxia'
print(s.name) # 'geekxia'
s.grade = '一年级' # 会报错
-
__slots__
只对当前类的实例起作用,对其继承的子类不起作用。如果希望子类也能有这样的限制,需给子类自身添加__slots__
限制。
2、Getters 与 Setters
class Student(object):
# getter
@property
def score(self):
return self._score
# setter
@score.setter
def score(self, score):
if not isinstance(score, int):
raise ValueError('分数必须是int类型')
if score<0 or score>100:
raise ValueError('分数必须在0~100之间')
self._score = score
@property
def grade(self):
return '一年级'
s = Student()
s.score = 90
print(s.score) # 90
print(s.grade) # '一年级'
-
@property
可以把一个getter方法变成属性,在getter方法前加上该装饰器即可。 -
@xxx.setter
可以把一个setter方法变成属性,在setter方法前加上该 装饰器即可。 - 未设置
@xxx.setter
装饰器的属性,是只读属性,上述代码中的grade
就是只读属性。
3、多重继承
Python支持多重继承,即一个类允许有多个父类,这是一种Mixin的设计手法。研究下面这个例子:
# 动物基类
class Animal(object):
pass
# 会跑的,基类
class RunnableMixin(object):
pass
# 会飞的,基类
class FlyableMixin(object):
pass
# 哺乳动物,继承自Animal
class Mammal(Animal):
pass
# 鸟类,继承自Animal
class Bird(Animal):
pass
# 狗:哺乳动物、会跑
class Dog(Mammal, RunnableMixin):
pass
# 蝙蝠:哺乳动物、会飞
class Bat(Mammal, FlyableMixin):
pass
# 鹦鹉:鸟类、会飞
class Parrot(Bird, FlyableMixin):
pass
# 鸵鸟:鸟类、会跑
class Ostrich(Bird, RunnableMixin):
pass
在设计类的继承关系时,通常主线都是单一继承下来的,例如Ostrich->Bird->Animal
。当需要“混入(Mixin)”额外的功能时,通过多重继承来实现,额外功能的类通常在命名时习惯加上Mixin
,如RunnableMixin
,FlyableMixin
。
4、特殊函数与定制类
形如__xxx__
的变量或者函数名就要注意,这些在Python中是有特殊用途的,可以帮助我们定制类。
-
__slots__
用于限制实例对象可以扩展哪些属性。 -
__len__
方法能让class作用于len()函数。 - 更多特殊函数请参见 Python官方文档。
(1)__str__
和__repr__
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student实例对象 (name: %s)' % self.name
__repr__ = __str__
s = Student('geekxia')
print(s) # Student实例对象 (name: geekxia)
当打印实例对象时,__str__
指定了打印的内容。
在交互式控制台,直接输出实例对象时,__repr__
指定了显示的内容,常用于调试。
(2)__iter__
和__next__
如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现一个__iter__
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__
方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
# 斐波那契数列 类
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
f = Fib()
for n in f:
print(n)
# 依次打印:
# 1
# 。。。。。
# 46368
# 75025
(3)__getitem__
上述Fib实例虽然能作用于for循环,看起来和list有点像,但使用Fib()[5]
或Fib()[5:10]
的方式取值时会报错,这就需要定义__getitem__
了。
# 斐波那契数列 类
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
f = Fib()
print(f[5]) # 8
print(f[:10]) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
如果把实例对象当成 dict 看,那么还可以定义两个特殊函数,分别是__setitem__
和__delitem__
。我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
(4)__getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。使用__getattr__
可以增强类的健壮性,示例如下:
class Student(object):
def __init__(self, name):
self.name = name
def __getattr__(self, attr):
# 属性
if attr == 'score':
return 100
# 方法
if attr == 'age':
return lambda x:x+1
raise AttributeError('Student类,没有 %s 属性' % attr)
s = Student('geekxia')
print(s.name) # 'geekxia'
print(s.score) # 100
print(s.age(20)) # 21
print(s.grade) # AttributeError: Student类,没有 grade 属性
(5)__call__
在Python中,能否直接调用一个对象本身呢?答案是肯定的,使用__call__
函数可以实现。callable()
函数可以判断一个对象是否是“自身可调用”的对象。示例代码如下:
class Apple(object):
def __init__(self,color):
self.color = color
def __call__(self, arg):
print('我是苹果:'+arg)
a = Apple('red')
# 调用实例对象本身
print(a('哈哈')) # 我是苹果: 哈哈
# callable() 检测一个对象是否可被调用
print(callable(a)) # True
print(callable(Apple('green'))) # True
print(callable(max)) # True
print(callable([1,2,3])) # False
print(callable(None)) # False
print(callable('hello')) # False
5、使用 Enum 枚举类
当需要定义一组常量时,更好办法是使用枚举类(Enum
)。比如一年12个月,示例如下:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
print(Month.Jan) # 'Month.Jan'
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
# Jan => Month.Jan , 1
# Feb => Month.Feb , 2
# Mar => Month.Mar , 3
# Apr => Month.Apr , 4
# May => Month.May , 5
# Jun => Month.Jun , 6
# Jul => Month.Jul , 7
# Aug => Month.Aug , 8
# Sep => Month.Sep , 9
# Oct => Month.Oct , 10
# Nov => Month.Nov , 11
# Dec => Month.Dec , 12
# value属性则是自动赋给成员的int常量,默认从1开始计数。
如果需要更精确地控制枚举类型(自定义这些常量的值),可以从Enum派生出自定义类。比如一周7天,示例如下:
from enum import Enum, unique
# 定义Week类,继承自Enum
# @unique装饰器可以帮助我们检查保证没有重复值。
@unique
class Week(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
print(Week.Sun) # 'Week.Sun'
print(Week['Mon']) # 'Week.Mon'
print(Week.Tue.value) # 2
print(Week(1)) # 'Week.Mon'
for name, member in Week.__members__.items():
print(name, '=>', member, ',', member.value)
# Sun => Week.Sun , 0
# Mon => Week.Mon , 1
# Tue => Week.Tue , 2
# Wed => Week.Wed , 3
# Thu => Week.Thu , 4
# Fri => Week.Fri , 5
# Sat => Week.Sat , 6
6、使用元类来创建类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
(1)使用type()
函数来创建类
type()函数除了可以检测对象和变量的类型,还可以动态地创建类,示例代码如下:
def fn(self, name='World'):
print('Hello, %s.' % name)
# 使用 type() 创建 Hello类
Hello = type('Hello', (object,), dict(hello=fn))
h = Hello()
h.hello() # 'Hello, World.'
h.hello('GeekXia') # 'Hello, GeekXia.'
print(type(Hello)) # <class 'type'>
print(type(h)) # <class '__main__.Hello'>
type()函数创建Hello类,依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
(2)使用 metaclass 元类来创建类
# 定义一个元类
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
# 根据上面的元类,创建一个新的类
class MyList(list, metaclass=ListMetaclass):
pass
# 测试一下
ml = MyList()
ml.add(1)
ml.add(2)
ml.add(3)
print(ml) # [1, 2, 3]
- 除了使用
type()
动态创建类以外,如果还想控制类的创建行为,还可以使用metaclass来创建类。 - metaclass元类允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
- 按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass。
-
__new__
方法接收到的参数依次是:当前准备创建的类的对象、类的名字、类继承的父类集合、类的方法集合。 - 元类是类的模板,类是实例的模板。
参考资源:
1、廖雪峰Python教程
2、Python官方文档
END!!!
网友评论