str

  1. >>> class Student(object):
  2. ... def __init__(self, name):
  3. ... self.name = name
  4. ... def __str__(self):
  5. ... return 'Student object (name: %s)' % self.name
  6. ...
  7. >>> print(Student('Michael'))
  8. Student object (name: Michael)

repr

虽然字符化的结果修改类,但将实例直接输出的结果并不好看

  1. >>> s = Student('Michael')
  2. >>> s
  3. <__main__.Student object at 0x109afb310>

这是因为直接显示变量调用的不是 __str__() ,而是 __repr__()__repr__() 返回程序开发者看到的字符串,解决方法只需将__repr__() 指向 __str()__ 即可。

  1. class Student(object):
  2. def __init__(self, name):
  3. self.name = name
  4. def __str__(self):
  5. return 'Student object (name=%s)' % self.name
  6. __repr__ = __str__

iter

甚至可以通过 __iter__ 方法定义一个可迭代的类型,同时我们还需定义 __next()__ 方法,在迭代到最后时报出 StopIteration 错误。

  1. class Fib(object):
  2. def __init__(self):
  3. self.a, self.b = 0, 1 # 初始化两个计数器a,b
  4. def __iter__(self):
  5. return self # 实例本身就是迭代对象,故返回自己
  6. def __next__(self):
  7. self.a, self.b = self.b, self.a + self.b # 计算下一个值
  8. if self.a > 100000: # 退出循环的条件
  9. raise StopIteration()
  10. return self.a # 返回下一个值
  11. >>> for n in Fib():
  12. ... print(n)
  13. ...
  14. 1
  15. 1
  16. 2
  17. 3
  18. 5
  19. ...
  20. 46368
  21. 75025

getitem

可以通过定义 __getitem__() 方法,来使对象可索引化。

  1. class Fib(object):
  2. def __getitem__(self, n):
  3. a, b = 1, 1
  4. for x in range(n):
  5. a, b = b, a + b
  6. return a
  7. >>> f = Fib()
  8. >>> f[0]
  9. 1
  10. >>> f[1]
  11. 1
  12. >>> f[2]
  13. 2

定义切片方法:

  1. class Fib(object):
  2. def __getitem__(self, n):
  3. if isinstance(n, int): # n是索引
  4. a, b = 1, 1
  5. for x in range(n):
  6. a, b = b, a + b
  7. return a
  8. if isinstance(n, slice): # n是切片
  9. start = n.start
  10. stop = n.stop
  11. if start is None:
  12. start = 0
  13. a, b = 1, 1
  14. L = []
  15. for x in range(stop):
  16. if x >= start:
  17. L.append(a)
  18. a, b = b, a + b
  19. return L
  20. >>> f = Fib()
  21. >>> f[0:5]
  22. [1, 1, 2, 3, 5]

getattr

当用不存在的属性时,python会通过 __getattr__ 方法来定义该属性

  1. class Student(object):
  2. def __init__(self):
  3. self.name = 'Michael'
  4. def __getattr__(self, attr):
  5. if attr=='score':
  6. return 99
  7. >>> s = Student()
  8. >>> s.name
  9. 'Michael'
  10. >>> s.score
  11. 99

好处是可以将对象的属性和方法的定义全部动态化,不需要手动一一生成。

练习

利用完全动态的getattr,写出一个链式调用:

  1. class Chain(object):
  2. def __init__(self, path=''):
  3. self._path = path
  4. def __getattr__(self, path):
  5. return Chain('%s/%s' % (self._path, path))
  6. def __str__(self):
  7. return self._path
  8. __repr__ = __str__

执行命令

  1. Chain().status.user.timeline.list

输出为:

  1. /status/user/timeline/list

程序执行过程如下:

  1. 初始化 Chain()path=""self._path=''
  2. 由于Chain().status 未定义,因此执行 __getattr__() ,其中:
    • self._path='' (这由第1步可知)
    • path="status" (注意 status 就是 __getattr__() 输入参数中的 path)
    • 返回 Chain("/status")
  3. 初始化 Chain()path="/status" ,该 Chain 实例的 _path="/status"
  4. 由于Chain().user 未定义,因此执行 __getattr__() ,其中:
    • self._path="/status"
    • path="user"
    • 返回 Chain("/status/user")
  5. 初始化 Chain()path="/status/user" ,该 Chain 实例的 _path="/status/user"
  6. 类似的流程一直递归运行到最后,返回 Chain("/status/user/timeline/list")
  7. 再次初始化 Chain()self._path="/status/user/timeline/list"
  8. 执行 __str__() ,定义 str(self)=self._path
  9. 由于 __repr__() 指向 __str__() ,因此 Chain().status.user.timeline.list 的输出为该Chain 实例的 _path,为 "/status/user/timeline/list"

当我们执行:

  1. Chain().users('michael').repos

结果会报错:TypeError: 'Chain' object is not callable

这是因为在执行完 Chain().user 的定义后,
Chain().users('michael') 相当于执行 Chain("/user")('michael')
很明显,我们的Chain类型并不是可调用的,所以需要再额外定义一个__call__()方法:

  1. def __call__(self, path):
  2. return Chain('%s/%s' % (self._path,path))

这样就能顺利得到输出:

  1. Chain().users('michael')