• 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("上下文管理协议")
    

      

  • 相关阅读:
    BZOJ 1492 [NOI2007]货币兑换Cash
    BZOJ 2285 [Sdoi2011]保密
    BZOJ 2962 序列操作
    BZOJ 3197 [Sdoi2013]assassin
    BZOJ 3131 [Sdoi2013]淘金
    BZOJ 3532 [Sdoi2014]Lis
    BZOJ 1923 [Sdoi2010]外星千足虫
    BZOJ 4855 [Jsoi2016]轻重路径
    BZOJ 4289 PA2012 Tax
    微信开发之 微信支付
  • 原文地址:https://www.cnblogs.com/Nicholas0707/p/9458021.html
Copyright © 2020-2023  润新知