Python之路(第二十七篇) 面向对象进阶:内置方法描述符

Posted Nicholas--Altshuler

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python之路(第二十七篇) 面向对象进阶:内置方法描述符相关的知识,希望对你有一定的参考价值。

一、__call__

 

对象后面加括号,触发执行类下面的__call__方法。

创建对象时,对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

  class Foo:
  ?
      def __call__(self, *args, **kwargs):
          print("我执行啦")
  ?
  f = Foo()
  f()  #对象加括号调用执行类下的__call__方法
  #输出结果 我执行啦
 

  

 

二、__next____iter__实现迭代器协议

 

迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)

 

可迭代对象执行obj.__iter__()得到的结果就是迭代器对象。

在类中,如果有__iter____next__内置方法,那么就构成了迭代器。

例子

  
  class Foo:
  ?
      def __init__(self,n):
          self.n = n
  ?
      def __iter__(self):
          return self  #实例本身就是迭代对象,故返回自己
  ?
      def __next__(self):
          if self.n >10:
              raise StopIteration   #如果超过10就报StopIteration 错误
          self.n = self.n + 1
          return self.n
  ?
  f = Foo(7)
  for i in f:  #for循环自动调用__next__方法,实现了迭代取值
      print(i) 
 

  

例子2

输出100内的斐契那波数列

  
  class F:
  ?
      def __init__(self):
          self.a = 0
          self.b = 1
  ?
      def __iter__(self):
          return self
  ?
      def __next__(self):
          self.a ,self.b = self.b , self.a + self.b
          if self.a > 100:
              raise StopIteration
          return self.a
  ?
  f = F()
  for i in f:
      print(i)
 

  

 

三、描述符(__get__,__set__,__delete__)

描述符(descriptor):

1、描述符本质

就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。__get__():调用一个属性时,触发__set__():为一个属性赋值时,触发__delete__():采用del删除属性时,触发

 

2、描述符的作用

是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

描述符是在另外一个类的类属性进行定义的,描述符在一个类的类属性__dict__字典里

 

例子1

  class Foo:
  ?
      def __get__(self, instance, owner):
          print("执行了__get__")
  ?
      def __set__(self, instance, value):
          print("执行了__set__")
  ?
      def __delete__(self, instance):
          print("执行了__delete__")
  ?
  ?
  class Bar:
      x = Foo()
  ?
      def __init__(self,name):
          self.name = name
  ?
  ?
  b = Bar("nick")
  b.x     #调用执行描述符里的__get__方法
  print(b.x)  #
  b.x = 1  # 调用执行描述符里的__set__方法
  print(b.__dict__)
  del b.x  #调用执行描述符里的__delete__方法
  print(b.__dict__)

  

 

输出结果

  执行了__get__
  执行了__get__
  None
  执行了__set__
  {‘name‘: ‘nick‘}
  执行了__delete__
  {‘name‘: ‘nick‘}

  

 

例子2

  
  #描述符Str
  class Str:
      def __get__(self, instance, owner):
          print(‘Str调用‘)
      def __set__(self, instance, value):
          print(‘Str设置...‘)
      def __delete__(self, instance):
          print(‘Str删除...‘)
  ?
  #描述符Int
  class Int:
      def __get__(self, instance, owner):
          print(‘Int调用‘)
      def __set__(self, instance, value):
          print(‘Int设置...‘)
      def __delete__(self, instance):
          print(‘Int删除...‘)
  ?
  class People:
      name=Str()
      age=Int()
      def __init__(self,name,age): #name被Str类代理,age被Int类代理,
          self.name=name
          self.age=age
  ?
  #何地?:定义成另外一个类的类属性
  ?
  #何时?:且看下列演示
  ?
  p1=People(‘alex‘,18)
  ?
  #描述符Str的使用
  p1.name
  p1.name=‘egon‘
  del p1.name
  ?
  #描述符Int的使用
  p1.age
  p1.age=18
  del p1.age
  ?
  #我们来瞅瞅到底发生了什么
  print("__p1.__dict__",p1.__dict__)
  print(People.__dict__)
  ?
  #补充
  print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的
  print(type(p1).__dict__ == People.__dict__)

  

输出结果

  
  Str设置...
  Int设置...
  Str调用
  Str设置...
  Str删除...
  Int调用
  Int设置...
  Int删除...
  __p1.__dict__ {}
  {‘__module__‘: ‘__main__‘, ‘name‘: <__main__.Str object at 0x021C6850>, ‘age‘: <__main__.Int object at 0x021C6870>, ‘__init__‘: <function People.__init__ at 0x021C5DB0>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘People‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘People‘ objects>, ‘__doc__‘: None}
  True
  True
 

  

3、描述符分两种

(1) 数据描述符:至少实现了__get__()__set__()

 

  ?
  class Foo:
      def __set__(self, instance, value):
          print(‘set‘)
      def __get__(self, instance, owner):
          print(‘get‘)

  

(2) 非数据描述符:没有实现__set__()

  
  class Foo:
      def __get__(self, instance, owner):
          print(‘get‘)

  

 注意:非数据描述符一般是只有__get__,如果保留__delete__执行会报错。

4、 注意事项:

 

(1)描述符本身应该定义成新式类,被代理的类也应该是新式类(python3中全部是新式类)

(2)必须把描述符定义成另外一个类的类属性,不能为定义到构造函数中,

(3)要严格遵循该优先级,优先级由高到底分别是

a.类属性b.数据描述符c.实例属性d.非数据描述符e.找不到的属性触发__getattr__()

 

例子1

  
  class Foo:
  ?
      def __get__(self, instance, owner):
          print("执行了__get__")
  ?
      def __set__(self, instance, value):
          print("执行了__set__")
  ?
      def __delete__(self, instance):
          print("执行了__delete__")
  ?
  class People:
  ?
      name = Foo()
  ?
      def __init__(self,name):
          self.name = name
  ?
  ?
  p = People("nick")
  People.name  = "nick"  #调用执行了描述符的__set__方法,这一步类属性由之前的描述符被定义成另外一个字符串,
  # 所以下面再次调用就无法再次使用描述符了
  People.name   
  ?
  #可以得出结论,类属性的优先级大于数据描述符

  

 

例子2

  
  class Foo:
  ?
      def __get__(self, instance, owner):
          print("执行了__get__")
  ?
      def __set__(self, instance, value):
          print("执行了__set__")
  ?
      def __delete__(self, instance):
          print("执行了__delete__")
  ?
  class People:
  ?
      name = Foo()
  ?
      def __init__(self,name):
          self.name = name
  ?
  ?
  p = People("nick")  #实例化对象,调用数据描述符的__set__,
  # 但是由于描述符的__set__只是执行了打印操作,什么都没做,所以p对象的__dict__什么都没有
  p.name = "nicholas"
  print(p.__dict__) #输出的结果为空
  ?
  #因此可以得出结论,数据描述符的优先级大于实例属性(字典操作)

  

 

例子3

  class Foo(object):
      def __init__(self):
          pass
  ?
      def __get__(self, instance, owner):
          print("执行了__get__")
  ?
  class People(object):
  ?
      name = Foo("x")
  ?
      def __init__(self,name,age):
          self.name = name
          self.age = age
  ?
  ?
  ?
  p = People("nick",18)  #实例化对象,这里由于是非数据描述符,优先级低于实例属性,
  # 所以这里直接设置了实例属性,而不再调用描述符
  print(p.name)  #打印直接输出实例属性
  print(p.__dict__)
  #输出的结果:{‘name‘: ‘nick‘, ‘age‘: 18}
  ?
  #因此可以得出结论,实例属性的优先级大于非数据描述符

  

 

例子4

  
  class Foo(object):
      def __init__(self,name2):
          self.name2 = name2
  ?
      def __get__(self, instance, owner):
          print("执行了__get__")
  ?
  ?
  class People(object):
  ?
      name = Foo("x")
  ?
      def __init__(self,name,age):
          self.name = name
          self.age = age
  ?
      def __getattr__(self, item):
          print("__getattr__")
  ?
  ?
  p = People("nick",18)  #实例化对象,这里由于是非数据描述符,优先级低于实例属性,
  # 所以这里直接设置了实例属性,而不再调用描述符
  print(p.name)
  print(p.sex)  #调用不存在的属性执行了__getattr__
  print(p.__dict__)
  #输出的结果:{‘name‘: ‘nick‘, ‘age‘: 18}

  

 

5、描述符的应用

例子1

  
  class Type:
  ?
      def __init__(self,key,expect_type):
          self.key = key
          self.expect_type = expect_type
  ?
      def __get__(self, instance, owner):
          print("执行__get__方法")
          print(self)                #这里的self就是type类的对象
          print(instance)            #这里的instance就是传入的People类的对象
          print("执行__get__方法")
          return  instance.__dict__[self.key]   #通过instance的字典获取对象的属性值
  ?
      def __set__(self, instance, value):
          print("执行__set__方法")
          instance.__dict__[self.key] = value   #instance是另一个类的对象,这里要设置对象的属性字典
  ?
      def __delete__(self, instance):
          print("执行__delete__方法")
          instance.__dict__.pop(self.key)  #删除对象的属性
  ?
  class People:
      name = Type("name",str)
      age = Type("age",int)
  ?
      def __init__(self,name,age):
          self.name = name
          self.age = age
  ?
  p1 = People("nick",18)  #调用2次描述符,对对象的字典进行设置
  print(p1.name)  #通过数据描述符获取对象的属性值
  print(p1.__dict__)
  p1.age = 20  #调用描述符对对象进行设置
  print(p1.__dict__)

  

输出结果

  
  执行__set__方法
  执行__set__方法
  执行__get__方法
  <__main__.Type object at 0x004CB4F0>
  <__main__.People object at 0x02106DF0>
  执行__get__方法
  nick
  {‘name‘: ‘nick‘, ‘age‘: 18}
  执行__set__方法
  {‘name‘: ‘nick‘, ‘age‘: 20}

  


?

 

例子2

 

  
  class Type:
  ?
      def __init__(self,key,expect_type):
          self.key = key
          self.expect_type = expect_type
  ?
      def __get__(self, instance, owner):
          print("执行__get__方法")
          print(self)                #这里的self就是type类的对象
          print(instance)            #这里的instance就是传入的People类的对象
          print("执行__get__方法")
          return  instance.__dict__[self.key]   #通过instance的字典获取对象的属性值
  ?
      def __set__(self, instance, value):
          print("执行__set__方法")
          if not isinstance(value,self.expect_type):
              print("您输入的%s不是%s"%(self.key,self.expect_type))
              raise TypeError
          instance.__dict__[self.key] = value   #instance是另一个类的对象,这里要设置对象的属性字典
  ?
      def __delete__(self, instance):
          print("执行__delete__方法")
          instance.__dict__.pop(self.key)  #删除对象的属性
  ?
  class People:
      name = Type("name",str)
      age = Type("age",int)
  ?
      def __init__(self,name,age):
          self.name = name
          self.age = age
  ?
  p1 = People("nick",18)  #调用2次描述符,对对象的字典进行设置
  print(p1.name)  #通过数据描述符获取对象的属性值
  print(p1.__dict__)
  p1.age = 20  #调用描述符对对象进行设置
  print(p1.__dict__)
  # p1.name = 11  #通过描述符的if not isinstance(value,self.expect_type)判断属性的类型
  ?
  # p2 = People(88,18)  #通过描述符的if not isinstance(value,self.expect_type)判断属性的类型

  

 

 

四、__enter____exit__

打开文件操作用 with open() as f操作,这叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter____exit__方法。

__enter__(self):当with开始运行的时候触发此方法的运行

__exit__(self, exc_type, exc_val, exc_tb):当with运行结束之后触发此方法的运行

exc_type如果抛出异常,这里获取异常的类型

exc_val如果抛出异常,这里显示异常内容

exc_tb如果抛出异常,这里显示所在位置

 

用途或者说好处:

1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处

 

 例子

class OPEN:

    def __init__(self,name):
        self.name = name

    def __enter__(self):
        print("执行__enter__")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("执行__exit__")
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        print("执行__exit__2222")

with OPEN("a.txt") as f:
    print(f)  #执行打印__enter__内置方法,同时打印内置方法返回的结果

#with 语句结束时执行__exit__方法,没有错误则打印None,有错误则打印错误的信息
print("上下文管理协议")

  

 


以上是关于Python之路(第二十七篇) 面向对象进阶:内置方法描述符的主要内容,如果未能解决你的问题,请参考以下文章

Python之路第十七篇:Django进阶篇

Python之路第十七篇:Django进阶篇

Python之路(第二十六篇) 面向对象进阶:内置方法

Python之路第十七篇:Django进阶篇

Python之路第十七篇:Django进阶篇

Python之路第十七篇:Django之进阶篇