使用__slots__
class Student(object):passs = Student()s.name = 'Michael' # 动态给实例绑定一个属性# 还可以给实例绑定一个方法:def set_age(self, age): # 定义一个函数作为实例方法slef.age = agefrom types import MethodTypes.set_age = MethodType(set_age, s) # 给实例绑定一个方法s.set_age(25) # 调用实例方法print(s.age) #25
但是,给一个实例绑定的方法对另一个实例是不起作用的:
>>> s2 = Student() # 创建新的实例>>> s2.set_age(25) # 尝试调用方法Traceback (most recent call last):File "<stdin>", line 1, in <module>AttributeError: 'Student' object has no attribute 'set_age'
为了给所有实例都绑定方法,可以给class绑定方法:
def set_score(self, score):self.score = scoreStudent.set_score = set_score# 给class绑定方法后,所有实例均可调用s.set_score(100)s2.set_score(99)
但是,如果想要限制实例的属性,比如只允许对Student实例添加name和age属性。
为了达到限制的目的,Python允许在定义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 "<stdin>", line 1, in <module>AttributeError: 'Student' object has no attribute 'score'
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类不起作用:
>>> class GraduateStudent(Student):... pass...>>> g = GraduateStudent()>>> g.scpre = 99
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的** __slots__ **加上父类的**__slots__**。
class A():__slots__ = (name, age)class B(A):__slots__ = (score)# B的是实例的限制属性实际是(name, age, score)
使用@property
设置属性前检查参数
class Student(object):def get_score(self):return self._scoredef 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
这样,对任意的Student实例进行操作,就不能随心所欲地设置score了:
>>> 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内置的@property装饰器就是负责把一个方法变成属性调用的:
class Student(object):# 把一个getter方法变成属性,只需要加上@property就可以了@propertydef score(self):return self._score# @property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值@score.setterdef 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 = values = Student()s.score = 60 # 实际转换成s.set_score(60)print(s.score) # 实际转换成s.get_score()s.score = 9999 # 报错
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
# birth是可读写属性,而age就是一个只读属性class Student(object):@propertydef birth(self):return self._birth@birth.setterdef birth(self, value):self._birth = value@propertydef age(self):return 2015 - self._birth
多重继承
class Animal(object):passclass Runnable(object):def run(self):print('Running...')# 通过多重继承,一个子类就可以同时获得多个父类的所有功能。class Dog(Animal, Runnable):pass# 为了更好地看出继承关系, 我们把Runnable改为RunnableMixInclass Dog(Animal, RunnableMixIn):pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
定制类
__str__
自定义打印出来的实例内容:
class Student(object):def __init__(self, name):self.name = namedef __str__(self): # __str__()返回用户看到的字符串return 'Student object (name: %s)' % self.name# __repr__()返回程序开发者看到的字符串,为调试服务,通常和__str__()一样__repr__ = __str__print(Student('Michael')) # Student object (name: Michael)
__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,bdef __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 n in Fib():... print(n)...11235...4636875025
__getitem__
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:
>>> Fib()[5]Traceback (most recent call last):File "<stdin>", line 1, in <module>TypeError: 'Fib' object does not support indexing
要表现得像list那样按照下标取出元素,需要实现getitem()方法:
class Fib(object):def __getitem__(self, n):a, b = 1, 1for x in range(n):a, b = b, a + breturn a>>> f = Fib()>>> f[0]1>>> f[10]89>>> f[100]573147844013817084101
上面的代码只是实现一个简单的取出元素操作,若要实现list的切片方法(如:list(range(100))[5:10], f[:10:2]),还有很多的工作要做。
此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。
与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
__getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。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的值
返回函数也是完全可以的:
class Student(object):def __getattr__(self, attr):if attr == 'age':return lambda: 25# 只是调用方式要变为s.age() # 25
注意,只有在没有找到属性的情况下,才调用**__getattr__**,已有的属性,比如name,不会在__getattr__中查。
此外,注意到任意调用如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
class Student(object):def __getattr__(self, attr):if attr=='age':return lambda: 25raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
(后面有点看不懂,到原文去看吧。)
__call__
任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。
class Student(object):def __init__(self, name):self.name = namedef __call__(self):print('My name is %s' % self.name)s = Student('Michael')s() # My name is Michael
__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
如果把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例:
# 通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。callable(Student()) # Truecallable(max) # Truecallable([1,2,3]) # Falsecallable(None) # Falsecallable('str') # False
使用枚举类
当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份:
JAN = 1FEB = 2MAR = 3...NOV = 11DEC = 12
好处是简单,缺点是类型是int,并且仍然是变量。
更好的方法是为这样枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:
from enum import EnumMonth = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')))for name, member in Month.__members__.items():print(name, '=>', member, ',', member.value)Jan => Month.Jan , 1Feb => Month.Feb , 2Mar => Month.Mar , 3Apr => Month.Apr , 4May => Month.May , 5Jun => Month.Jun , 6Jul => Month.Jul , 7Aug => Month.Aug , 8Sep => Month.Sep , 9Oct => Month.Oct , 10Nov => Month.Nov , 11Dec => Month.Dec , 12
value属性则是自动赋给成员的int常量,默认从1开始计数。
如果需要更精确的控制枚举类型,可以从Enum派生出自定义类:
from enum import Enum, unique@uniqueclass Weekday(Enum):Sun = 0 # Sun的Vaule被设定为0Mon = 1Tue = 2Web = 3Thu = 4Fri = 5Sat = 6
@unique装饰器可以帮助我们检查保证没有重复值。
访问这些枚举类型可以有若干种方法:
day1 = Weekday.Monprint(day1) # Weekday.Monprint(day1.value) # 1print(day1 == Weekday(1)) # Trueprint(Weekday['Wed']) # Weekday.Wedprint(Weekday(1)) # Weekday.MonWeekday(7)# Traceback (most recent call last):# ...# ValueError: 7 is not a valid Weekdayfor name, merber in Weekday.__members__.items():print(name, '=>', merber, ',', merber.value)# Sun => Weekday.Sun , 0# Mon => Weekday.Mon , 1# Tue => Weekday.Tue , 2# Wed => Weekday.Wed , 3# Thu => Weekday.Thu , 4# Fri => Weekday.Fri , 5# Sat => Weekday.Sat , 6
既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
使用元类
type()
class Hello(object):def hello(self, name='world'):print('Hello, %s.' % nameh = Hello()h.hello() # Hello, world.print(type(Hello)) # <class 'type'>print(type(h)) # <class 'hello.Hello'>
type()函数可以查看一个类型或变量的类型,Hello是一个class,他的类型就是type,而h是一个实例,他的类型就是classHello。
我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:
def fn(self, name='world'): # 先定义函数print('Hello, %s.' % name)Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello classh = Hello()h.hello() # Hello, world.print(type(Hello)) # <class 'type'>print(type(h)) # <class '__main__.Hello'>
要创建一个class对象,type()函数依次传入3个参数:
- class的名称
- 继承的父类集合。注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元数写法,
(object,) - class的方法名称与函数绑定。这里我们把函数
fn绑定到方法
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
metaclass
metaclass直译为元类,先定义metaclass,就可以创建类,最后创建实例。
所以metaclass允许创建或修改类,可以把类看成是metaclass创建出来的“实例”。
metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到。
mateclass为自定义的MyList增加一个add方法:
定义ListMetaclass,(按照默认习惯,metaclass的类名总是以Metaclass结尾):
# 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时,要通过ListMetaclss.__new__()来创建,在此可以修改类的定义,比如加上新的方法,然后返回修改后的定义。
__new__()方法接收到的参数依次是:
- 当前准备创建的类的对象
- 类的名字
- 类继承的父类集合
- 类的方法集合
测试一下MyList是否可以调用add方法:
L = MyList()L.add(1)print(L) # [1]# 而普通的list没有add()方法
动态修改有什么意义?直接在MyList定义中写上add()方法不是更简单吗?正常情况下,确实应该直接写,通过metaclass修改纯属变态。
但是,总会遇到需要通过metaclass修改类定义的。ORM就是一个典型的例子。
ORM全称”Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样写代码更简单,不用直接操作SQL语句。
要编写一个ORM框架,所有的类都只能童泰定义,因为只有使用者才能根据表的结构定义出对应的类来。
…(更多的内容看原文吧,我放弃,SQL都不懂呢,感觉看不懂下面的了,告辞)
链接
