使用slots
正常情况下,当我们定义了一个Class,并创建了一个关于Class的instance后,我们可以该instance绑定任何属性和方法,这表现了动态语言的灵活性。
class Student(object):
pass
给instance绑定属性:
>>>s = Student()
>>>s.name = 'Michael'
>>>print('s.name')
Michael
给instance绑定一个方法:
>>> 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
注意,给一个instance绑定的方法对另一个instance不成立
>>>s2 = Student
>>>s2.set_age(25)
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Student' object has no attribute 'set_age'
为了解决这个,给所有的instance都绑定方法,我们选择给Class绑定方法
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
给Class绑定方法后,所有的instance都可以调用。这体现了动态语言的优点。
使用slots(限制instance的属性)
为了达到限制的目的,可以再定义Class的时候,定义一个特殊的slots变量,来限制该Class实例可以添加的属性。
class Student(object):
__slots__ = ('name','age') #用tuple定义允许绑定的属性名
验证:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Student' object has no attribute 'score'
注意,slots定义的属性仅仅对当前类起作用,对集成的子类不起作用。除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。
使用@property
为了解决在绑定属性时,虽然将属性直接暴露出去但是无法检查参数的问题(导致例子中的成绩可以随意修改)
s = Student()
s.score = 9999
为了解决这样的问题可以添加方法来get和set
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
这样就可以:
>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
为了简化,使类的属性即可以被检查又可以通过访问属性的简单方式进行访问,可以使用Python内置的装饰器:
class Student(object):
"""docstring for Student"""
@property
def score(self):
return self._score
@score.setter
def score(self,value):
if not isinstance(value,int):
raise ValueError('score mmust be an integer')
if value < 0 or value > 0:
raise ValueError('score must between 0-100')
self._score = value
实质就是将一个方法变成属性,方便访问。
把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
定义只读属性的方法:只定义getter方法,不定义setter方法
多重继承
继承是面向对象编程的三个重要特点之一,因为通过继承,子类就可以扩展父类的功能。
# 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的功能,可以定义相应的类:
class Runnable(object):
def run(self):
print('Running...')
class Flyable(object):
def fly(self):
print('Flying...')
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:
class Dog(Mammal,Runnable):
pass
对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:
class Bat(Mammal, Flyable):
pass
Mixln
在设计类的继承关系时,通常主线都是单一继承下来的,但是如果需要“混入”特殊的功能,通过多重继承就可以实现(Mixln),这是因为通过多重继承子类就可以同时获得多个父类的功能。
为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn,类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:
class Dog(Mannal,RunnableMixln,CarnivorousMixln):
pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
定制类
除了之前介绍过的slots和len方法之外还有很多有特殊用途的函数可以帮我们定制类。
str
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> print(Student('Michael'))
<__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
...
>>> print(Student('Michael'))
Student object (name: Michael)
但是直接敲变量不用print打印出来的实例还是不好看:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)
这是因为直接显示变量调用的不是str(),而是repr(),两者的区别是str()返回用户看到的字符串,而repr()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。
解决方案就是直接将事先定义好的str赋值给repr
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
iter
如果一个类想被用于for... in循环,就必须实现一个iter()方法,该方法返回一个迭代的对象,然后python的for循环就会不断的调用该迭代的next()方法得到循环的下一个值,知道StopIteration.
例如:
class Fib(object):
def __init__(self):
self.a, selff.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
上面的例子中的instance虽然可以yongfor循环,但是不可以完全把它当做list处理:
>>> Fib()[5]
Traceback (most recent call last):
File "", line 1, in
TypeError: 'Fib' object does not support indexing
为了取出下标元素,需要实现getitem方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
为了进一步的实现list中的slice操作:
#我们必须让__getitem__()判断传入的参数是int还是slice
class Fib(object):
"""docstring for Fib"""
def __getitem__(self, n):
if isinstance(n, int):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice):
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
如果要让getitem实现对step参数和负数的处理还需要进一步的完善。
getattr
正常情况下,当调用类的方法或属性时,如果不存在就会报错。
例子:
class Student(object):
def __init__(self):
self.name = 'Michael'
#调用name属性,没有问题,但是调用不存在的score属性就有问题了
>>> s = Student()
>>> print(s.name)
Michael
>>> print(s.score)
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'score'
要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个getattr()方法,动态返回一个属性。修改如下:
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
当调用不存在的属性时,比如score,Python解释器会试图调用getattr(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
注意,只有在没有找到属性和的情况下才调用_getattr,已有的属性不会在__getattr中查找。
注意到任意调用如s.abc都会返回None,这是因为我们定义的getattr默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
call
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用,也可以通过call直接在instance上调用(不用显式的写出method)。
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
#调用方式
>>>s = Student('Michael')
>>>s()
My name is Michael
除此之外,call()还可以定义参数,这让对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者本身就没什么根本的区别。
更多的时候我们判断一个对象是否可以被调用,能被调用的对象是一个Callable对象:
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False
使用枚举类
当我们定义常量的时候,可以使用大写变量通过整数来定义:
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12
但是变量的类型仍然是int。
更好的方法是为这样的枚举类型定义一个Class类型,每个变量都是Class的一个唯一的实例
#获得Month类型的枚举类
from enum import Enum
Month = Enum ('Month',('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
#可以直接使用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 = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
使用元类
type()
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
前情提要:已经写好了hello.py的Module:
class Hello(object):
def hello(self,name='world'):
print('Hello,%s.' %name)
当Python解释器载入hello Module使,就会依次执行该Module的所有语句,执行结果就是动态的创建一个Hello的class对象(类也是一种对象)。
>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
>>> print(type(h))
type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。
type()函数可以返回一个对象的类型,又可以创建出新的类型。
#可以通过type()函数创建出Hello类,而无需通过之前的方法
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
>>> print(type(h))
要创建一个Class对象,type()函数依次传入3个参数:
1.class的名称
2.继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法
3.class的方法名称与函数绑定
通过type()函数创建的类和直接写Class是一样的,因为Python解释器遇到Class定义时,仅仅是扫面一下class定义的语法,然后调用type()函数创建出class
正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
metaclass
除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass:先定义metaclass,就可以创建类,最后创建instance(可以将类看成是metaclass创建的实例)
例子:使用metaclass给自定义的Mylist增加一个add方法
#metaclass是类的模板,必须从type类派生
class ListMetaclass(type):
def __new__(cls,name,bases,attrs):
attrs['add'] = lambda self,value : self.append(value)
return type.__new__(cls,name,bases,attrs)
有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字metaclass:
class Mylist(list,metaclass=ListMetaclass):
pass
当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
new()方法接收到的参数依次是:
当前准备创建的类的对象;
类的名字;
类继承的父类集合;
类的方法集合。
#测试:
>>> L = MyList()
>>> L.add(1)
>> L
[1]
#普通的list没有add()方法
>>> L2 = list()
>>> L2.add(1)
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'list' object has no attribute 'add'
元类的方法可以用在写ApI或者数据库中的ORM(object relationship mapping)等方面。
为了加深理解,附上一个知乎上的回答:
[type和object的关系]https://www.zhihu.com/question/38791962/answer/78172929