在这之前, 请确保所有的类都继承于 object !!!
在这之前, 请确保所有的类都继承于 object !!!
在这之前, 请确保所有的类都继承于 object !!!
__slots__ (限制动态属性)
作为一种动态语言, python 支持 在类实例化后, 为实例动态添加属性/方法的功能.
Copy class Student ( object ):
pass
s = Student ()
s . name = 'Michael' # 动态给实例绑定一个属性
print s . name
然而在某些情况下, 我们可能并不希望这样用, 此时, 可以通过类的 __slots__ 变量, 来限制上面说到的功能.
Copy class Student ( object ):
__slots__ = ( 'name' , 'age' ) # 用tuple定义允许绑定的属性名称
s = Student () # 创建新的实例
s . name = 'Michael' # 绑定属性'name'
s . age = 25 # 绑定属性 'age'
s . score = 99 # 绑定未在 **slots** 中指定的属性 'score'
当我们运行 s.score = 99
时, 就会出错:
Copy Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于 'score' 没有被放到 __slots__ 中, 所以不能绑定 score 属性, 试图绑定 score 将得到 AttributeError 的错误
使用 __slots__ 要注意, __slots__ 定义的属性仅对当前类 起作用, 对继承的子类是不起作用的, 除非在子类中也定义 __slots__ , 这样, 子类允许定义的属性就是自身的 __slots__ 加上 父类的 __slots__ (注意, 是加上 ! 加上 ! 加上 ! 重要的事说三遍).
@property (神奇的 getter/setter)
在 Java 中, 有一种 JavaBean 规范, 即所有属性对外部那是 private 的, 想要访问/设置, 必须通过 getter 和 setter 方法.
这在 Python 中有更好的实现, 那就是 property , 它是一个装饰器, 负责把一个方法变成属性 调用.
需要注意一点的是, 属性要定义成 private 或 protected (如 _score 或 __score).
Copy 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
把一个 getter 方法变成属性, 只需要加上 @property 就可以了, 此时, @property 本身又创建了另一个装饰器 @xxx.setter , 负责把一个 setter 方法变成属性赋值, 于是, 我们就拥有一个可控的属性操作:
Copy >>> 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 方法就是一个只读属性:
Copy class Student ( object ):
@ property
def birth ( self ):
return self . _birth
@birth . setter
def birth ( self , value ):
self . _birth = value
@ property
def age ( self ):
return 2014 - self . _birth
上面的 birth 是可读写属性, 而 age 就是一个只读属性.
__str__ 与 __repr__ (打印类)
当我们需要打指定 print 类或者直接输入类时的输出内容, 就可以定制 __str__ 和 __repr__ , 直接显示变量调用的是 __str__ , print 时调用的是是 __repr__ .
Copy 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 循环, 类似 list 或 tuple 那样, 就必须实现一个 __iter__ 方法, 该方法返回一个迭代对象, 然后, Python 的 for 循环就会不断调用该迭代对象的 next() 方法拿到循环的下一个值, 直到遇到 StopIteration 错误时退出循环.
我们以斐波那契数列为例, 写一个 Fib 类, 可以作用于 for 循环:
Copy 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 # 返回下一个值
使用结果如下:
Copy >>> for n in Fib ():
... print n
...
1
1
2
3
5
...
46368
75025
__call__ (实例自调用)
当调用一个类的实例方法时, 可以用 xxx.method() 来调用, 那能不能直接在实例本身上调用呢, 像 xxx() 这样?
任何类, 只需要定义一个 __call__ 方法, 就可以直接对实例进行调用.
Copy class Student ( object ):
def __init__ ( self , name ):
self . name = name
def __call__ ( self ):
print ( 'My name is %s .' % self.name)
调用方式如下:
Copy >>> s = Student ( 'Michael' )
>>> s ()
My name is Michael .
__call__ 还可以定义参数, 对实例进行直接调用就好比对一个函数进行调用一样, 所以你完全可以把对象看成函数, 把函数看成对象.
如果需要判断一个对象是否能被调用, 通过 callable() 函数就可以了.
Copy >>> callable ( Student ())
True
__getattr__ (动态生成属性)
正常情况下, 当我们调用类的方法或属性时, 如果不存在, 就会报错. 比如定义 Student 类:
Copy class Student ( object ):
def __init__ ( self ):
self . name = 'Michael'
调用 name 属性, 没问题, 但是, 调用不存在的 score 属性, 就有问题了:
Copy >>> s = Student ()
>>> print s . name
Michael
>>> print s . score
Traceback (most recent call last):
...
AttributeError : 'Student' object has no attribute 'score'
错误信息很清楚地告诉我们, 没有找到 score 这个 attribute.
要避免这个错误, 除了可以加上一个 score 属性外, Python 还有另一个机制, 那就是写一个 __getattr__ 方法, 动态返回一个属性. 修改如下:
Copy class Student ( object ):
def __init__ ( self ):
self . name = 'Michael'
def __getattr__ ( self , attr ):
if attr == 'score' :
return 99
当调用不存在的属性时, 比如 score, Python 解释器会试图调用 __getattr__ (self, 'score') 来尝试获得属性, 这样, 我们就有机会返回score的值:
Copy >>> s = Student ()
>>> s . name
'Michael'
>>> s . score
99
返回函数 也是完全可以的:
Copy class Student ( object ):
def __getattr__ ( self , attr ):
if attr == 'age' :
return lambda : 25
只是调用方式要变为:
注意, 只有在没有找到属性的情况下, 才调用 __getattr__ , 已有的属性, 比如 name, 不会在 __getattr__ 中查找.
此外, 注意, 如果我们访问一个类中没有定义, 又没有在 __getattr__ 中定义的属性, 就会返回 None , 这是因为我们定义的 __getattr__ 默认返回就是 None, 所以按照约定, 应该 AttributeError 的错误:
Copy class Student ( object ):
def __getattr__ ( self , attr ):
if attr == 'age' :
return lambda : 25
raise AttributeError ( '\'Student\' object has no attribute \' %s \'' % attr)
问题?
我们完成可以为类直接定义新属性, 使用 __getattr__ 方法到底有什么用处呢?
举个例子:
(参考: 廖雪峰老师教程 )
现在很多网站都搞 REST API , 比如新浪微博、豆瓣啥的:
Copy http://api.server/user/friends
http://api.server/user/timeline/list
如果要为这些 API 提供 SDK, 那不是要给每个 URL 对应的 API 都写一个方法? 这还不得累死, 而且, API 一旦改动, 我们写的 SDK 也要改...
这里就可以利用完全动态的 __getattr__ , 我们可以写出一个链式调用:
Copy 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
试试:
Copy >>> Chain().status.user.timeline.list
'/status/user/timeline/list'
这样, 无论 API 有多少, 后面有多少改动, 我们都可以通过上面这种链式调用的方法去访问!
还有些 REST API 会把参数放到 URL 中, 比如 GitHub 的 API:
Copy GET /users/:user/repos
调用时,需要把 :user
替换为实际用户名, 此时可以配合 __call__
写出链式调用:
Copy class Chain ( object ):
def __init__ ( self , path = '' ):
self . _path = path
def __call__ ( self , parameter ):
if parameter is not None :
return Chain ( ' %s / %s ' % (self._path, parameter))
return self
def __getattr__ ( self , resource ):
return Chain ( ' %s / %s ' % (self._path, resource))
def __str__ ( self ):
return self . _path
试试:
Copy >>> Chain().users('michael').repos
'/users/michael/repos'
__getitem__、__setitem__、__delitem__
通过这三个方法, 可以将自己定义的类表现得和内置的 list、tuple、dict 没什么区别, 但这么做的话, 还有很多工作要做, 以后需要用到的时候再来补充吧.