1, 概览
看到类似__slots__这种形如__xxx__的变量或者函数名就要注意, 这些在 Python 中是有特殊用途的.
__slots__我们已经知道怎么用了,__len__() 方法我们也知道是为了能让 class 作用于 len() 函数.
除此之外, Python 的 class 中还有许多这样有特殊用途的函数, 可以帮助我们定制类.
1.1,__str__() 和 __repr__()
1,__str__()
修改 print(instance) 显示的值
- # 正常打印 instance
- >>> print (s)
- <__main__.Student object at 0x00000005AD1EAAC8>
- # 在类中加入__str__() 方法
- ... def __str__(self):
- ... return 'Student object (name: %s)' % self.name
- # 再次打印
- >>> print(s)
- Student object (name: Bob)
- 2,__repr__()
修改 instance 显示的值
- # 正常 instance 显示
- >>> s
- <__main__.Student object at 0x00000005AD1EAAC8>
- # 在类中加入__str__() 方法
- ... def __repr__(self):
- ... return 'Student object (name: %s)' % self.name
- # 再次打印
- >>>s
- Student object (name: Bob)
__str__() 和 __repr__() 的区别
__str__() 返回用户看到的字符串, 而__repr__() 返回程序开发者看到的字符串
通常__str__() 和__repr__() 代码都是一样的. 所以可以这样写
- def __str__(self):
- return 'Student object (name=%s)' % self.name
- __repr__ = __str__
- 1.2,__iter__()
我们知道, 只有 iterable 对象可以进行 for...in... 循环.
如果想让一个类被用于 for 循环, 就需要__iter__() 方法. 该方法返回一个 iterable 对象. 然后, 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(): # for 循环打印 Fib() 实例
- ... print(n)
- 1.3,__getitem__()
1,__getitem__() 方法, 可以实现 实例像 list 一样的下标取值
- # 类的编写
- 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] # 0 就是传入的参数 n, 返回的值是 a 的值
- 1
注:
虽然 f 可以像 list 一样, 进行下标取值, 但不能进行切片.
原因是__getitem__() 传入的参数可能是一个 int, 也可能是一个切片对象 slice, 所以要做判断:
2,__getitem__() 方法, 实现 实例的切片
- # 类的编写
- class Fib(object):
- def __getitem__(self, n):
- if isinstance(n, int): # n 是索引
- a, b = 1, 1
- for x in range(n):
a, b = b, a + b
- return a
- if isinstance(n, slice): # 判断 n 是否是切片
- 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: # 判断 a 值 是否应该加入 L
- L.append(a)
a, b = b, a + b # a 的值一直再变. 只是上面 if 要判断是否保存 a 值
- return L
- # 调用
- >>> f = Fib()
- >>> f[0:5]
- [1, 1, 2, 3, 5]
总结
Fib(), 现在可以下标取值 或 s 切片取值, 但切片的处理没有步长和负数, 所以, 要正确实现一个__getitem__() 还是有很多工作要做的.
此外, 如果把对象看成 dict,__getitem__() 的参数也可能是一个可以作 key 的 object, 例如 str.
与之对应的是__setitem__() 方法, 把对象视作 list 或 dict 来对集合赋值. 最后, 还有一个__delitem__() 方法, 用于删除某个元素.
1.3, 关于切片对象 slice 的思考
slice 一般是跟在 list 后面的. 所以不能通过判断切片对 list 的操作, 如 L[0:5] 这样的表达式, 来判断 slice 的数据类型.
在 Python 中, 有一个 slice 对象, 它的类型就是 slice. 所以猜想:
上面的代码中的 f[0:5],n==[0:5].__getitem__(), 会把 [0:5], 解释成 slice(0,5,none). 从而判断传入的参数是 slice.
n 是 slice 的实例, 有 start,stop 属性. 所以通过 start = n.start ,stop = n.stop, 来获取 slice 开始和结束的范围
1.4,__getattr__()
正常情况下, 当我们调用类的方法或属性时, 如果不存在, 就会报错. 那么, 我们可以通过__getattr__() 方法, 动态返回一个属性.
__getattr__() 方法返回属性的前提是, 在__getattr__() 的函数体里, 这个属性符合你设置的条件
1, 动态返回属性
- class Student(object):
- def __init__(self):
- self.name = 'Michael'
- def __getattr__(self, attr):
- if attr=='score':
- return 99
- # 我们只定义了 name 属性, 如果尝试获取 score 属性, 就会交由__getattr__() 方法处理
- >>> s.score
- 99
- # 如果请求的属性不符合__getattr__() 方法的判断条件呢
- >>> s.gender # 无显示. 正常会报错
- >>> print(s.gender) # 显示 None, 正常会报错
- None
2, 动态返回函数
- class Student(object):
- def __getattr__(self, attr):
- if attr=='age':
- return lambda: 25
- # 调用
- >>> s.age()
- 25
3, 抛出错误
使用__getattr__() 方法后, 如果 某属性 class 里没定义,__getattr__() 里也没有, 是不会抛出错误的. 针对这些属性, 如果要正常抛出错误, 需要在__getattr__() 方法里定义
- class Student(object):
- def __getattr__(self, attr):
- if attr=='age':
- return lambda: 25
- raise AttributeError('\'Student\'object has no attribute \'%s\'' % attr)
- 1.5,__call__()
我们调用实例方法时, 我们用 instance.method() 来调用. 而定义一个__call__() 方法, 就可以直接对实例进行调用
- class Student(object):
- def __init__(self, name):
- self.name = name
- def __call__(self):
- print('My name is %s.' % self.name)
- # 调用
- >>> s() # self 参数不要传入
My name is Michael.
__call__() 还可以定义参数. 对实例进行直接调用就好比对一个函数进行调用一样, 所以你完全可以把对象看成函数, 把函数看成对象, 因为这两者之间本来就没啥根本的区别.
callable() 函数, 可以判断一个对象是否是 "可调用" 对象.
- >>> callable(max)
- True
- >>> callable([1, 2, 3])
- False
2, 例题
1, 利用完全动态的__getattr__, 写出一个链式调用. 完全动态的生成 URL
- class Chain(object):
- def __init__(self,path=""): # 初始化实例, Chain().path 为空
- self._path=path
- def __getattr__(self,path): # 使用类没有定义的属性, 就调用
- return Chain("%s/%s"%(self._path,path))
- def __call__(self,path): # 直接对实例进行调用, 将实例当作类似函数一样调用
- return Chain("%s/%s"%(self._path,path))
- def __str__(self): # 实例显示的值
- return self._path
- __repr__=__str__
- # 调用
- print(Chain().a.b.user("ChenTian").c.d)
- /a/b/user/ChenTian/c/d
调用解析:
创建了一个实例 Chain().
Chain().a, 类没有 a 属性, 调用__getattr__() 方法, 将 实例名和属性名传进去, 返回一个 Chain(/a) 实例
Chain(/a).b, 操作同上, 返回一个 Chain(/a/b) 实例
Chain(/a/b).user("ChenTian"), 先会执行 getattr 返回 Chain 实例, Chain(/a/b/user("ChenTian"))
然后由于有__call__方法, 可以直接对实例调用. 此时就会调用__call__方法. 传入的 path="ChenTian".
然后返回 Chain(/a/b/user/ChenTian)
Chain(/a/b/user/ChenTian).c.d 操作同第 2 步
来源: http://www.bubuko.com/infodetail-2603057.html