xiyou 发表于 2018-8-6 12:25:37

Python学习笔记__7.4章定制类

  # 这是学习廖雪峰老师python教程的学习笔记
  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 就是传入的参数 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
  

[*]  总结
  Fib(),现在可以下标取值 或s切片取值,但切片的处理没有步长和负数,所以,要正确实现一个__getitem__()还是有很多工作要做的。
  此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。
  与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。
  1.3、关于切片对象slice的思考
  slice一般是跟在list后面的。所以不能通过判断切片对list的操作,如L 这样的表达式,来判断slice的数据类型。
  在Python中,有一个slice对象,它的类型就是 slice。所以猜想:
  上面的代码中的f,n==。__getitem__(),会把,解释成 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 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()
  False
  2、例题
  1、利用完全动态的__getattr__,写出一个链式调用。完全动态的生成URL
  class Chain(object):
  def __init__(self,path=&quot;&quot;): # 初始化实例,Chain().path 为空
  self._path=path
  def __getattr__(self,path): # 使用类没有定义的属性,就调用
  return Chain(&quot;%s/%s&quot;%(self._path,path))
  def __call__(self,path):# 直接对实例进行调用,将实例当作类似函数一样调用
  return Chain(&quot;%s/%s&quot;%(self._path,path))
  def __str__(self):# 实例显示的值
  return self._path
  __repr__=__str__
  # 调用
  print(Chain().a.b.user(&quot;ChenTian&quot;).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(&quot;ChenTian&quot;),先会执行getattr返回Chain实例,Chain(/a/b/user(&quot;ChenTian&quot;))
  然后由于有__call__方法,可以直接对实例调用。此时就会调用__call__方法。传入的path=&quot;ChenTian&quot;。
  然后返回Chain(/a/b/user/ChenTian)

[*]  Chain(/a/b/user/ChenTian).c.d 操作同第2步
页: [1]
查看完整版本: Python学习笔记__7.4章定制类