__ slots __
如何在外部给class绑定方法?
def set_score(self, score):
self.score = score
Student.set_score = set_score
动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
但是,如果我们想要限制动态添加的属性名,比如:只允许给Student市里添加name属性,此时就需要用到__ slots __
变量。
Python允许在定义class的时候,定义一个特殊的__ slots __
变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name') # 用tuple定义允许绑定的属性名称
使用__ slots __
要注意,__ slots __
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:子类实例可以动态添加除name外的其他属性
@property
前面提到我们可以在类里定义公有属性也可以在实例外直接动态绑定实例属性,然后直接赋值,调用。这样做的坏处是赋值时没办法检查参数的类型,所以我们运用类封装的特性,把这些属性的赋值与调用都通过实例方法或者类方法来实现,不对其进行直接操作。
>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。
怎么简化这种步骤呢,Python内置的@property
装饰器就是负责把一个方法变成属性调用的:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def 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
raise显示引发异常的方法,程序出错时,python会自动触发异常,也可以通过raise显示引发异常;且一旦执行了raise语句,raise之后的语句不在执行;但需注意如果加入了try,except,那么except里的语句会被执行
try:
s = None
if s is None:
print('s是空对象') #执行
raise NameError
print(len(s)) #不执行
except Exception:
print('空对象没有长度') #执行
把一个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
方法
例如:
class Rectangle():
@property
def width(self):
return self._width
@width.setter
def width(self,value):
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
self._height = value
@property
def area(self):
return self._width * self._height
rect = Rectangle()
rect._width = 10
rect._height = 20
print(rect.area) #200
多重继承
一个子类可以有多个基类
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:
class Dog(Mammal, Runnable):
pass
MixIn
为了清楚地让人看出继承的主次关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn
的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了MixIn
。举个例子,Python自带了TCPServer
和UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn
和ThreadingMixIn
提供。通过组合,我们就可以创造出合适的服务来。
class MyTCPServer(TCPServer, ForkingMixIn): #多进程的TCP服务
pass
定制类
__ len __()
len()
函数是用来返回list,tuple,dict,se
t的长度;如果一个类表现得像一个dict
,要获取有多少个元素,就得用 len()
函数。
要让len()
函数工作正常,类必须提供一个特殊方法 __ len __()
,它返回元素的个数。
例如,我们写一个 Students 类,把名字传进去:
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3
__ str __()
如果类里没有使用__ str __
,则打印类时,会打印出
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)
__ repr __()
和__ str __()
方法类似,只不过前者是用在print方法里的,而后者是在交互模式下直接敲类名输出时用到的。
__ iter __()
如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__ next __()
方法拿到循环的下一个值,直到遇到StopIteration
错误时退出循环。
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 n in Fib():
print(n)
__ getitem __()
结合上面代码中的Fib()实例,它可以通过for循环,但却不可以像list一样直接靠下标来取,如:Fib()[2],要想用下标来取值则需要实现getitem()方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
>>> f = Fib()
>>> f[0]
__ getattr __()
当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个getattr()方法,动态返回一个属性。
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
s = Student()
print(s.name) #Michael
print(s.a) #None
如果木有写getattr()方法
class Student(object):
def __init__(self):
self.name = 'Michael'
# def __getattr__(self, attr):
# if attr=='score':
# return 99
s = Student()
print(s.name) #Michael
print(s.a) #None
会报如下错误:
$ python new.py
Michael
Traceback (most recent call last):
File "new.py", line 55, in <module>
print(s.a) #None
AttributeError: 'Student' object has no attribute 'a'
当调用不存在的属性时,Python解释器会试图调用\_\_getattr\_\_(self, 'score')
来尝试获得属性.
任意调用如s.abc(既不在实例里,又没在__getattr__
里)都会返回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)
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。
这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。
举个例子:
现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似:
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。
利用完全动态的__getattr__
,我们可以写出一个链式调用:
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'
这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!
__ call __
任何类,只需要定义一个call()方法,就可以直接对实例进行调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable
对象,通过callable()
函数,我们就可以判断一个对象是否是“可调用”对象
>>> callable(Student())
True
__ dict __
int, list, dict等这些常用的数据类型是没有__ dict __属性的
class A(object):
a = 0
b = 1
def __init__(self):
self.d = 4
def test(self):
print ('a normal func')
a = A()
a.a =3
print(A.__dict__)
print(a.__dict__)
得到的结果是
$ python new.py
{'__module__': '__main__', 'a': 0, 'b': 1, '__init__': <function A.__init__ at 0x00000204DB41B1E0>, 'test': <function A.test at 0x00000204DB444378>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
{'d': 4, 'a': 3}
__setattr__
会拦截所有属性的的赋值语句
class Foo(object):
def __init__(self,name):
self.storage = name
def __setattr__(self, key, value):
self.storage={'k1':'v1'}
print(key,value)
def __getattr__(self, item):
print(item)
obj = Foo("123")
obj.x = 123
会报错:RecursionError: maximum recursion depth exceeded
原因:当初始化的时候,self.storage,对象调用storage就会自动执行__setattr__方法,
然后__setattr__方法里面又是对象调用属性,就会再执行setattr,这样就是无限递归了。
为了避免这个问题需要用下面这种方式实现:
class Dict(object):
def __init__(self, name):
self.name = name
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self.__dict__['name'] = value
d = Dict("Mike")
d.name= "韩某米"
枚举
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 22 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
print(Weekday.Sun.value) #22
@unique装饰器可以帮助我们检查保证没有重复值。
元类
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 class
要创建一个class对象,type()函数依次传入3个参数:
class的名称;
继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
metaclass
:先定义metaclass,然后创建类,直译为元类,
网友评论