在这之前, 请确保所有的类都继承于 object !!!
在这之前, 请确保所有的类都继承于 object !!!
在这之前, 请确保所有的类都继承于 object !!!
__slots__ (限制动态属性)
作为一种动态语言, python 支持 在类实例化后, 为实例动态添加属性/方法的功能.
class Student(object): pass s = Student() s.name = 'Michael' # 动态给实例绑定一个属性 print s.name
然而在某些情况下, 我们可能并不希望这样用, 此时, 可以通过类的 __slots__ 变量, 来限制上面说到的功能.
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
时, 就会出错:
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).
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 方法变成属性赋值, 于是, 我们就拥有一个可控的属性操作:
>>> 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 方法就是一个只读属性:
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__.
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 循环:
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 ... 1 1 2 3 5 ... 46368 75025
__call__ (实例自调用)
当调用一个类的实例方法时, 可以用 xxx.method() 来调用, 那能不能直接在实例本身上调用呢, 像 xxx() 这样?
任何类, 只需要定义一个 __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() My name is Michael.
__call__ 还可以定义参数, 对实例进行直接调用就好比对一个函数进行调用一样, 所以你完全可以把对象看成函数, 把函数看成对象.
如果需要判断一个对象是否能被调用, 通过 callable() 函数就可以了.
>>> callable(Student()) True
__getattr__ (动态生成属性)
正常情况下, 当我们调用类的方法或属性时, 如果不存在, 就会报错. 比如定义 Student 类:
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 这个 attribute.
要避免这个错误, 除了可以加上一个 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
返回函数也是完全可以的:
class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25
只是调用方式要变为:
>>> s.age()
25
注意, 只有在没有找到属性的情况下, 才调用 __getattr__, 已有的属性, 比如 name, 不会在 __getattr__ 中查找.
此外, 注意, 如果我们访问一个类中没有定义, 又没有在 __getattr__ 中定义的属性, 就会返回 None, 这是因为我们定义的 __getattr__ 默认返回就是 None, 所以按照约定, 应该 AttributeError 的错误:
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, 比如新浪微博、豆瓣啥的:
http://api.server/user/friends http://api.server/user/timeline/list
如果要调用这些 API, 那不是要给每个 URL 对应的 API 都写一个方法? 这还不得累死, 而且, API 一旦改动, 我们写的方法也要改…
这里就可以利用完全动态的 __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
试试:
>>> Chain().status.user.timeline.list '/status/user/timeline/list'
这样, 无论 API 有多少, 我们都可以通过上面这种链式调用的方法去访问!
__getitem__、__setitem__、__delitem__
通过这三个方法, 可以将自己定义的类表现得和内置的 list、tuple、dict 没什么区别, 但这么做的话, 还有很多工作要做, 以后需要用到的时候再来补充吧.