wlyyb521 发表于 2018-8-11 06:42:50

python第九课 面向对象

  上周习题:
  1、shape基类,要求所有子类都必须提供面积的计算,子类有三角形,矩形,圆,圆类的数据可序列化
  2、用面向对象实现LinkedList链表,单向链表实现append,iternodes
  3、双向链表实现append,pop,insert,remove,iternodes
  答案:
  1、shape类
  import math
  import json
  import msgpack
  class Shape:
  @property
  def area(self):
  raise NotImplementedError # 不可实例化的class
  class Triangle(Shape):
  def __init__(self,a,h):
  self.a = a
  self.h = h
  @property
  def area(self):
  return int(0.5*self.a*self.h)
  class Rectangle(Shape):
  def __init__(self,width,height):

  self.width =>
  self.height =>  @property
  def area(self):
  return self.width*self.height
  class Circle(Shape):
  def __init__(self,radius):
  self.d = radius ** 2
  @property
  def area(self):
  return '{:.3}'.format(math.pi * self.d)
  class SerializableMixin:
  def dumps(self,t='json'):
  if t == 'json':
  return json.dumps(self.__dict__)
  elif t == 'msgpack':
  return msgpack.packb(self.__dict__)
  else:
  raise NotImplementedError
  class SerializableCircleMixin(SerializableMixin,Circle):
  pass
  scm = SerializableCircleMixin(4)
  print(scm.area)
  print(scm.dumps())
  # s = scm.dumps('msgpack')
  # print(s)
  t = Triangle(3,4)
  r = Rectangle(3,4)
  c = Circle(3)
  print(t.area,r.area,c.area)
  2、单链表
  class SingleNode:
  def __init__(self,item,next=None):
  self.item = item # 定义链表单个元素
  self.next = next
  def __repr__(self):
  return repr(self.item) # 返回单个元素
  class LinkedList: # 定义链表
  def __init__(self):
  self.head = None # 定义链表元素开头
  self.tail = None # 定义链表元素结尾
  def add(self,item): # 实现append
  node = SingleNode(item) # 调用singlenode获取一个node
  if self.head is None: # 初始head为None
  self.head = node # 复制head为node
  else:
  self.tail.next = node # 后续定义尾部追加next元素
  self.tail = node # 更新tail值为node
  def iternodes(self): # 迭代
  current = self.head # 现在的值为head,第一个
  while current:
  yield current
  current = current.next # 更新current,完成迭代
  ll = LinkedList()
  ll.add('abc')
  ll.add(1)
  ll.add(2)
  ll.add('def')
  print(ll.head,ll.tail)
  for x in ll.iternodes():
  print(x)
  3、双向链表;
  class SingleNode:
  def __init__(self,item,prev=None,next=None):
  self.item = item
  self.prev = prev
  self.next = next
  def __repr__(self):
  return repr(self.item)
  class LinkedList:
  def __init__(self):
  self.head = None
  self.tail = None
  def append(self,item):
  node = SingleNode(item)
  if self.head is None:
  self.head = node
  else:
  self.tail.next = node
  node.prev = self.tail
  self.tail = node
  def insert(self,index,item):
  if index < 0:
  raise ValueError('Wrong index {}'.format(index))
  current = None
  for i ,node in enumerate(self.iternodes()):
  if index == i:
  current = node
  break
  if current is None:
  self.append(item)
  return
  node = SingleNode(item)
  prev = current.prev
  if prev is None:
  self.head = node
  else:
  prev.next = node
  node.prev = prev
  node.next = current
  current.prev = node
  def pop(self):
  if self.tail is None:
  raise Exception('Empty')
  node = self.tail
  item = node.item
  prev = node.prev
  if prev is None:
  self.head = None
  self.tail = None
  else:
  prev.next = None
  self.tail = prev
  return item
  def remove(self,index):
  if self.tail is None:
  raise Exception('Empty')
  if index < 0:
  raise ValueError('Wrong index {}'.format(index))
  current = None
  for i, node in enumerate(self.iternodes()):
  if i == index:
  current = node
  break
  if current is None:
  raise ValueError('Wrong index {} out of boundary'.format(index))
  prev = current.prev
  next = current.next
  if prev is None and next is None:
  self.head = None
  self.tail = None
  elif prev is None:
  self.head = next
  next.prev = None
  elif next is None:
  self.tail = prev
  prev.next = None
  else:
  prev.next = next
  next.prev = prev
  del current
  def iternodes(self,reverse=False):
  current = self.tail if reverse else self.head
  while current:
  yield current
  current = current.prev if reverse else current.next
  ll = LinkedList()
  ll.append(1)
  ll.append(2)
  ll.append(3)
  ll.append('abcd')
  ll.append(4)
  ll.append(5)
  ll.append('def')
  ll.append(8)
  print(ll.head,ll.tail)
  for x in ll.iternodes():
  print(x)
  print('~~~~~~~~~~~~~~~~~~')
  ll.remove(6)
  ll.remove(5)
  ll.remove(0)
  ll.remove(1)
  for x in ll.iternodes():
  print(x)
  print('~~~~~~~~~~~~~~~~~~')
  ll.insert(3,5)
  ll.insert(20,'def')
  ll.insert(1,2)
  ll.insert(0,'abc')
  for x in ll.iternodes():
  print(x)
  一、魔术方法:
  1、特殊属性
属性含义__name__类,函数,方法的名字__moudle__类定义所在的模块名__class__对象或类所属的类__bases__类的基类的元组,顺序为它们在基类列表中表现的顺序__doc__类,函数的文档字符串,如果没有定义则为None__mro__类的mro,class.mro( )返回的结果保存在__mro__中__dict__类或实例的属性,可写的字典  2、查看属性
  __dir__返回类或对象的所有成员名称列表。dir( )函数就是调用__dir__( )。如果提供__dir__( ),则返回属性的列表,否则会尽量在__dict__属性中收集信息
  dir()得到的属性多,是个列表
  class Person:
  pass
  p = Person()
  print(dir(p))
  print(p.__dict__)
  import test# test是上面的例子的文档名
  from test import Animal
  class Cat(Animal):
  X = 'cat'
  y = 'abcd'
  class Dog(Animal):
  def __dir__(self):
  return ['dog'] # 必须是列表
  print(dir())
  print(dir(test))
  print(sorted(object.__dict__.keys()))
  print(dir(Cat))
  3、魔术方法:
  1、分类:
  1、创建与销毁:__init__与__del__
  2、hash
  3、bool
  4、可视化
  5、运算符重载
  6、容器和大小
  7、可调用对象
  8、上下文管理
  9、反射
  10、描述器
  11、其他杂项
  2、hash
  方法:__hash__内建函数hash()调用的返回值,返回一个整数,如果定义这个方法,这个类的实例就可hash
  hash取mo法,%x得到的mo,就是hash值,如果x越大,越不容易冲突
  hash散列,散开的,很大的一个空间存放,缓存时使用,缓存的目的为了再看,hash算法的时间复杂度时O1,字典和集合的key
  class A:
  def __init__(self):
  self.a = 'a'
  self.b = 'b'
  def __hash__(self):   hash指向引用,虽然都是1,但内存地址不一样,不是一个
  return 1
  def __eq__(self, other):      eq方法指向内存地址,返回相等,则一模一样
  return self.a == other.a
  print(hash(A()))
  print((A(),A()))
  print({A(),A()})
  s = {A(),A()}
  print(s)
  3、eq
  方法:__eq__对应==操作符,判断两个对象是否相等,返回bool值
  __hash__方法只是返回一个hash值作为set的key,但是去重,还需要判断2个对象是否相等
  hash值相等,只是hash冲突,不能说明两个对象是相等的
  因此,一般来说,提供__hash__方法是为了作为set或者dict的key的,所以去重要同时进行的话,需要提供eq方法
  可hash对象必须提供__hash__方法,没有提供的话,isinstance(p1,collections.hashable)一定为False
  去重要提供eq方法
  设计二位坐标类Point,比较2个坐标是否相等?
  from collections import Hashable
  class Point:
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __hash__(self):
  return 1
  def __eq__(self, other):
  return self.x == other.x and self.y == other.y
  p1 = Point(1,2)
  p2 = Point(1,2)
  print(p1 is p2) # 内存地址不等       False
  print(p1 == p2) # 内容相等         True
  print({p1,p2})                  因为eq结果==为True所以去重,因为集合内部实现的是,先看是不是==,是就去重
  print(isinstance(p1,Hashable))
  4、bool
  方法:__bool__内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值
  没有定义__bool__( ),就找__len__( )返回长度,非0为真,如果len也没有定义,那么所有实例都返回为真
  class A:
  pass
  print(bool(A()))
  class B:
  def __bool__(self):
  return False
  print(bool(B))
  print(bool(B()))
  class C:
  def __len__(self):
  return 0
  print(bool(C()))
  5、可视化
方法意义__repr__内建函数repr( )对一个对象获取字符串表达,如果一个类定义了__repr__但没有定义__str__,那么在请求该类的实例的“非正式”的字符串表示时,也将调用__repr__( )__str__str()函数、内建函数format、print()函数调用,需要返回对象的字符串表达__bytes__bytes的时候,返回一个对象的bytes表达,即返回bytes对象  class A:
  def __init__(self):
  self.a = 'a'
  self.b = 'b'
  def __repr__(self):
  return 'repr:{},{}'.format(self.a,self.b)
  def __str__(self):                  # 在print,format、str时使用,这三个优先与repr
  return 'str:{},{}'.format(self.a,self.b)
  print(A()) # print函数使用str
  print() # []使用str,但其内部使用repr
  print() # []使用str,str也使用str
  print('str:a,b')
  s = 'b'
  print(['a'],(s,))
  6、运算符重载
  去int里找所有的运算符(在pycharm里)
         运算符      特殊方法      含义  <
  <=
  ==
  >
  >=
  !=
  __lt__
  __le__
  __eq__
  __gt__
  __ge__
  __ne__
比较运算符  +
  -
  *
  /
  %
  //
  **
  divmod
  __add__
  __sub__
  __mul__
  __truediv__
  __mod__
  __floordiv__
  __pow__
  __divmod__
算数运算符,移位,位运算也有对应的方法  +=
  -=
  *=
  /=
  %=
  //=
  **=
  __iadd__
  __isub__
  __imul__
  __itruediv__
  __imod__
  __ifloordiv
  __ipow__
  class A:
  def __init__(self,x):
  self.x = x
  def __sub__(self, other):
  return self.x - other.x
  def __isub__(self, other):
  tmp = self.x - other.x
  return A(tmp)
  a1 = A(1)
  a2 = A(2)
  print(a1 - a2)
  print(a2.__isub__(a1))
  a1 -= a2
  print(a1)
  练习:完成Point类设计,实现判断点相等的方法,并完成向量的加法
  class Point:
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __eq__(self, other):
  return self.x == other.x and self.y == other.y
  def add(self, other):
  return Point(other.x + self.x, other.y + self.y)
  def __add__(self, other):
  self.x = self.x + other.x
  self.y = self.y + other.y
  return Point(self.x, self.y)
  def __str__(self):
  return 'Point:{}:{}'.format(self.x, self.y)
  p1 = Point(1, 2)
  p2 = Point(1, 2)
  print(p1 == p2)
  print(p1 + p2)
  print(Point(*(p1, p2)))
  print(p1.add(p2))
  print(p1)
  运算符重载,往往是用面向对象实现的类,需要做大量的运算,而运算符可以实现实例的运算,加方法也可以
  7、容器相关方法:
            方法                                                意义__len__  内建函数len( ),返回对象的长度(>=0的整数),其实即便把对象当作容器
  类型看,就如同list或者dict,bool( )函数调用的时候,如果没有__bool__
  方法,则会看__len__方法是否存在,存在则返回非0为真。
__iter__迭代容器时调用,返回一个新的迭代器对象,必须时迭代器__contains__in成员运算符,没有实现,就调用__iter__方法遍历__getitem__  实现self访问。
  序列对象,key接受整数为索引,或者切片。对于set和dict,key为hashable。key不存在引发keyerror异常
__setitem__和__getitem__的访问类似,是设置值的方法__missing__字典使用__getitem__调用时,key不存在执行该方法  练习:将购物车类改造成方便操作的容器类
  class Item:
  def __init__(self, **kwargs):
  self.kwargs = kwargs
  def __repr__(self):
  return str(sorted(self.kwargs.items()))
  class Cart:
  def __init__(self):
  self.items = []
  def additem(self, item):
  self.items.append(item)
  def __len__(self):
  return len(self.items)
  def __iter__(self):
  return iter(self.items)
  def __getitem__(self, index):
  return self.items
  def __setitem__(self, key, value):
  self.items = value
  cart = Cart()
  cart.additem(1)
  cart.additem(2)
  cart.additem(3)
  cart.additem(Item(car='audi', price=270000))
  print(len(cart))
  for x in cart:
  print(x)
  print(2 in cart)
  cart = 'a'
  print(cart)
  8、可调用方法:
  方法:__call__ 类的第一个该方法,实例就可以像函数一样调用
  可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。
  class Point:
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __call__(self, *args, **kwargs):
  return 'Point({},{})'.format(self.x,self.y)
  p = Point(4,5)
  print(p)
  print(p())
  class Adder:
  def __call__(self, *args, **kwargs):
  ret = 0
  for x in args:
  ret += x
  self.ret = ret
  return ret
  adder = Adder()
  print(adder(4, 5))
  print(adder.ret)
  练习:定义一个斐波那契数列的类,方便调用,计算第n项
  class Fib:
  def __init__(self):
  self.items =
  def __call__(self, index):
  if index < 0:
  return None
  if index < len(self.items):
  return self.items
  for i in range(len(self.items), index+1):
  self.items.append(self.items + self.items)
  return self.items
  def __len__(self):
  return len(self.items)
  def __iter__(self):
  return iter(self.items)
  def __str__(self):
  return str(self.items)
  __repr__ = __str__
  fib = Fib()
  print(fib(30),len(fib))
  for x in fib:
  print(x)
  增加迭代的方法,返回容器的长度,使用容器减少计算,每次计算后保留容器,下次不用再计算
  9、上下文管理:
  1、文件IO操作可以对文件对象使用上下文管理,使用with...as语法
  2、with open('test') as f:pass
  3、仿照上例,可以写一个类,实现上下文管理
      方法                                                意义__enter__  进入与此对象相关的上下文,如果存在该方法,with语法会把该
  方法的返回值作为绑定到as语句的变量f的值
__exit__退出与此对象相关的上下文  class Point:
  def __init__(self):
  print('init')
  def __enter__(self):
  print('enter')
  def __exit__(self, exc_type, exc_val, exc_tb):
  print('exit')
  with Point() as f:
  print('do sth')
  实例化对象的时候,不会调用enter,进入with语句后,先enter,再with语句块,离开时exit
  with可以开启一个上下文运行环境,在执行前做准备工作,执行后做收尾工作
  4、上下文管理的安全性
  异常对上下文的影响
  class Point:
  def __init__(self):
  print('init')
  def __enter__(self):
  print('enter')
  def __exit__(self, exc_type, exc_val, exc_tb):
  print('exit')
  with Point() as f:
  raise Exception(''error)
  print('do sth')
  enter和exit照样执行,上下文管理很安全
  极端例子,import sys后,在with语句中加入sys.exit( )后,依然执行,init,enter,exit函数,哪怕是退出python环境
  __enter__方法的return值是with语句的变量,如果不返回就是None,一般返回self
  class Point:
  def __init__(self):
  print('init')
  def __enter__(self):
  print('enter')
  return self
  def __exit__(self, exc_type, exc_val, exc_tb):
  print('exit')
  p = Point()
  with p as f:
  print(p == f)
  print('do sth')
  5、__enter__和__exit__的参数:
  enter没有参数,exit有三个参数,都与异常有关
  如果上下文退出时都没有异常,这三个参数都是None,如果有异常,参数意义如下。
  如果有异常,参数意义如下:
  exc_type,异常类型
  exc_value,异常的值
  trackback,异常的追踪信息
  如果exit方法返回一个等效的True的值,则压制异常,否则,继续抛出异常,默认False
  练习:为加法函数计时
  1、使用装饰器
  2、使用上下文管理
  1、装饰器
  import datetime
  import time
  def logger(fn):
  def wrapper(*args):
  start = datetime.datetime.now()
  ret = fn(*args)
  delta = datetime.datetime.now()
  print('{} took {}s'.format(fn.__name__, (delta-start).total_seconds()))
  return ret
  return wrapper
  @logger
  def add(x, y):
  time.sleep(2)
  return x + y
  print(add(1, 2))
  2、上下文
  import datetime
  import time
  from functools import wraps
  class TimeIt:

  """This is A>  def __init__(self, fn=None):
  if fn is not None:
  self.fn = fn
  wraps(fn)(self)
  # self.__doc__ = fn.__doc__
  # def __enter__(self):
  # self.strat = datetime.datetime.now()
  # return self
  # 此上下为上下文管理
  # def __exit__(self, exc_type, exc_val, exc_tb):
  # delta = (datetime.datetime.now() - self.start).total_seconds()
  # print(delta)
  def __call__(self, *args, **kwargs):
  self.start = datetime.datetime.now()
  ret = self.fn(*args,**kwargs)
  self.delta = (datetime.datetime.now() - self.start).total_seconds()
  print(self.delta)
  return ret
  @TimeIt # add = TimeIt(add)
  def add(x, y):
  """This is add function"""
  time.sleep(2)
  return x + y
  print(add(2, 3))
  print(add.__doc__)
  print(TimeIt().__doc__)
  上面代码,既可以用在上下文管理,又可以用作装饰器
  上下文管理应用场景:
  1、增强功能
  在代码执行的前后增强代码,以增强其功能。类似于装饰器的功能
  2、资源管理
  打开了资源需要关闭,例如文件对象、网络连接、数据库连接等
  3、权限验证
  在执行代码之前,做权限的验证,在__enter__中处理
  二、反射
  程序运行时,区别于编译时,指的时程序被加载到内存执行的时候
  反射,reflection,指的是运行时,获取类型定义信息。
  一个对象,能够在运行时,像照镜子一样,反射出其类型信息
  具有反射能力的函数有,type(),isinstance( ),callable(),dir(),getattr()
  1、反射相关的函数和方法:
  有一个point类,查看它实例的属性,并修改它
  class Point:
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __str__(self):
  return 'Point({},{})'.format(self.x, self.y)
  def show(self):
  print(self.x, self.y)
  p = Point(3, 4)
  print(p)
  print(p.__dict__)
  p.__dict__['y'] = 16
  print(p.__dict__)
  p.z = 10
  print(p.__dict__)
  print(dir(p))
  print(p.__dir__())
  上例通过属性字典__dict__来访问对象的属性,本质也是反射,但是不好看
内建函数                                              意义  getattr(object,
  name[,default])
  通过name返回object的属性值。当属性不存在,将使用default返回,
  如果没有default,则抛出AttrbuteError。name必须是字符串
  setattr(object,
  name,value)
object的属性存在,则覆盖,不存在,新增  hasattr(object,
  name)
判断对象是否有这个名字的属性,name必须是字符串  用上面的方法来修改上例的代码
  class Point:
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __str__(self):
  return 'Point({}, {})'.format(self.x, self.y)
  def show(self):
  print(self)
  p1 = Point(1, 2)
  p2 = Point(2, 3)
  if not hasattr(Point, 'add'):
  setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))
  print(Point.add)
  print(p1.add)
  print(p1.add(p2))
  这种动态增加属性的方法,和装饰器修饰及Mixin的区别在于运行时动态改变类或者实例,更灵活
  练习:命令分发器,通过名称找到对应的函数执行。
  class Dispatcher:
  def cmd1(self):
  print('cmd1')
  def reg(self, cmd, fn):   # 注册,实现写入属性
  if isinstance(cmd, str):
  setattr(self.__class__,cmd,fn)
  def run(self):   # 运行时,实现获取函数
  while True:
  cmd = input('plz input command:')
  if cmd.strip() == 'quit':
  return
  getattr(self, cmd.strip(), self.defaultfn)() # 缺省为函数,需要调用
  def defaultfn(self):
  # print('defalut')
  print('default')
  dis = Dispatcher()
  dis.reg('cmd2', lambda self: print(1))
  dis.reg('cmd3', lambda self: print(2))
  dis.run()
  2、反射相关的魔术方法:
  __getattr__( ),__steattr__( ),__delattr__( )
  class Base:
  n = 0
  class Point(Base):
  z = 6
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def show(self):
  print(self.x, self.y)
  def __getattr__(self, item):
  return 'missing {}'.format(item)
  p1 = Point(4, 5)
  print(p1.x)
  print(p1.z)
  print(p1.n)
  print(p1.t)
  一个类的属性会按照继承关系找,如果找不到,就找getattr方法,如果没有这个方法,报attr错误
  class Base:
  n = 0
  class Point(Base):
  z = 6
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def show(self):
  print(self.x, self.y)
  def __getattr__(self, item):
  return 'missing {}'.format(item)
  def __setattr__(self, key, value):
  print('setattr {}={}'.format(key, value))
  self.__dict__ = value
  p = Point(1, 2)
  print(p.x)
  p.x = 50
  print(p.x)
  实例通过. 设置属性,如同self.x = x,就会调用__setattr__,属性要加到dict中,需要自己设置
  如果不设置,setattr会拦截对实例属性的增加,修改操作
  class Point:
  Z = 5
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __delattr__(self, item):
  print('can not del {}'.format(item))
  p = Point(1, 2)
  del p.x
  p.z = 15
  del p.z
  del p.Z
  del Point.Z
  print(p.Z)
  delattr会拦截实例删除属性的操作,但是通过类删除依然可以
  class Base:
  n = 0
  class Point(Base):
  z = 6
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __getattr__(self, item):
  return 'missing {}'.format(item)
  def __getattribute__(self, item):
  preturn item
  p = Point(1, 2)
  print(p.__dict__)
  print(p.x)
  print(p.z)
  print(p.m)
  print(Point.__dict__)
  实例的所有的属性访问,第一个都会调用getattribute方法,它阻止了属性的查找,返回计算后的值,或者抛出一个AttributeError
  它的ruturn值将作为属性查找的结果,如果抛出AttributeError,会调用getattr方法,因为表示属性未找到
  class Base:
  n = 0
  class Point(Base):
  z = 6
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __getattr__(self, item):
  return 'missing {}'.format(item)
  def __getattribute__(self, item):
  return object.__getattribute__(self, item)
  p = Point(1, 2)
  print(p.__dict__)
  print(p.x)
  print(p.z)
  print(p.m)
  print(Point.__dict__)
  __getattribute__方法中为了避免该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如object.getattribute(self,name)
  属性查找顺序:实例调用__getattribute__( )-->instance.__dict__-->instance.__class__.__dict__-->继承类的祖先直到object的__dict__-->调用__getattribute__
  二、描述器Descriptors
  描述器的表现
  用到三个魔术方法:__get__( )、__set__( )、__delete__( )
  方法如下:
  object.__get__(self,instance,owner)
  object.__set__(self,instance,owner)
  object__delete__(self,instance)
  self指代当前实例,调用者
  instance时owner的实例
  owner是属性的所属的类
  class A:
  def __init__(self):
  self.a1 = 'a1'
  print('A.init')
  def __get__(self, instance, owner):
  print('A.__get__ {} {} {}'.format(self,instance,owner))
  # return self   # 返回自身
  class B:
  x = A()
  def __init__(self):
  print('B.init')
  self.b = A()
  print('-'*20)
  print(B.x)
  # print(B.x.a1)    # 实例返回get的return,如果不写,None没有a1属性
  print('='*20)
  b = B()
  print(b.b)
  B类实例的属性不会触发get方法
  Python中,一个类实现了__get__、__set__、__delete__中的任何一个,就是描述器
  如果仅实现了__get__,就是非数据描述器
  同时实现了__get__、set就是数据描述器
  如果一个类的类属性设置为描述器,那么它被成为owner属主
  属性的访问顺序
  class A:
  def __init__(self):
  self.a1 = 'a1'
  print('A.init')
  def __get__(self, instance, owner):
  print('A.__get__ {} {} {}'.format(self, instance, owner))
  return self
  class B:
  x = A()
  def __init__(self):
  print('B.init')
  self.x = 'b.x' # 增加实例属性x
  print('-'*20)
  print(B.x)
  print(B.x.a1)
  print('='*20)
  b = B()
  print(b.x)
  print(b.x.a1) # AttributeError
  b是实例,b.x访问实例的属性,而不是描述器
  class A:
  def __init__(self):
  self.a1 = 'a1'
  print('A.init')
  def __get__(self, instance, owner):
  print('A.__get__ {} {} {}'.format(self, instance, owner))
  return self
  def __set__(self, instance, value):
  print('A.__set__ {} {} {}'.format(self, instance, value))
  self.data = value
  class B:
  x = A()
  def __init__(self):
  print('B.init')
  self.x = 'b.x' # 增加实例属性x
  print(B.x)
  print(B.x.a1)
  b = B()
  print(b.x)
  print(b.x.a1)
  print(B.x.__dict__)
  这次B.x.a1返回a1,访问到了描述器的数据
  实例的__dict__优先于非数据描述器
  数据描述器优先于实例的__dict__
  __delete__方法有同样的效果,有了这个方法,就是数据描述器
  本质(进阶)
  不是数据描述器优先级高,而是数据描述器把实例的属性从__dict__中拿走了,访问顺序没变过
  Python中的描述器
  练习:
  实现staticmethod非数据描述器
  class StaticMethod:
  def __init__(self, fn):
  self._fn = fn
  def __get__(self, instance, owner):
  return self._fn
  class A:
  @StaticMethod # stmtd = StaticMethod(stmtd) 属性等于类的实例
  def stmtd():
  print('static method')
  A.stmtd()
  A().stmtd()
  实现classmethod非数据描述器
  from functools import partial

  class>  def __init__(self, fn):
  self._fn = fn
  def __get__(self, instance, owner):
  ret = partial(self._fn, owner)
  return ret
  class A:

  @ClassMethod # clsmtd =>  def clsmtd(cls):
  print(cls.__name__)
  print(A.__dict__)
  A.clsmtd
  A.clsmtd()
  对实例的数据进行校验
  1、
  class Person:
  def __init__(self, name: str, age: int):
  self.params = ((name, str), (age, int))
  if not self.checkdata(self.params):
  raise TypeError()
  self.name = name
  self.age = age
  def checkdata(self, params):
  for p, t in params:
  if not isinstance(p, t):
  return False
  return True
  p = Person('tom', 18)
  print(p.checkdata(p.params))
  这种代码太过耦合,可以使用装饰器
  使用描述器
  class Typed:
  def __init__(self, name, type):
  self.name = name
  self.type = type
  def __get__(self, instance, owner):
  if instance is not None:
  return instance.__dict__
  return self
  def __set__(self, instance, value):
  if not isinstance(value, self.type):
  raise TypeError(value)
  instance.__dict__ = value
  class Person:
  name = Typed('tom', str)
  age = Typed(18, int)
  def __init__(self, name: str, age: int):
  self.name = name
  self.age = age
  p = Person('tom', 18)
  硬编码不好
  使用inspect模块
  import inspect
  class Typed:
  def __init__(self, item, type):
  self.item = item
  self.type = type
  def __get__(self, instance, owner):
  if instance is not None:
  return instance.__dict__
  return self
  def __set__(self, instance, value):
  if isinstance(value, self.type):
  instance.__dict__ = value
  else:
  raise TypeError(value)
  def typeassert(cls):
  params = inspect.signature(cls).parameters
  print(params)
  for name, param in params.items():
  print(param.name, param.annotation)
  if param.annotation != param.empty:
  setattr(cls, name, Typed(name, param.annotation))
  return cls
  @typeassert
  class Person:
  def __init__(self, name: str, age: int):
  self.name = name
  self.age = age
  def __repr__(self):
  return '{} is {}'.format(self.name, self.age)
  __str__ = __repr__
  p = Person('tom', 18)
  print(p)
页: [1]
查看完整版本: python第九课 面向对象