zoukankan      html  css  js  c++  java
  • 七夕节写那些结伴而行的特殊方法

    __getattr__和__setattr__

    这两个特别简单,__getattr__是通过属性操作符.或者反射getattr(),hasattr()无法获取到指定属性(对象,类,父类)的时候,该方法被调用

    __setattr__则是设置属性的时候被调用

    class A:
        def __getattr__(self, item):
    
            print('%s找不到这个属性'%item)
    
        def __setattr__(self, instance, value):
            print('设置了%s == %s'%(instance,value))
    
    a = A()
    a.aa # aa找不到这个属性
    hasattr(a,'bb') # bb找不到这个属性
    
    a.aa = 'a' # 设置了aa == a
    setattr(a,'bb','bb') # 设置了bb == bb
    

    与他相关的一个方法__getattribute__,尝试获取属性时总会调用这个方法(特殊属性或特殊方法除外),当该方法抛出AttributeError时才会调用__getattr__方法.

    class A:
        def __getattr__(self, item):
    
            print('%s找不到这个属性'%item)
    
        def __setattr__(self, instance, value):
            print('设置了%s == %s'%(instance,value))
        def __getattribute__(self, item):
            return 1
    a = A()
    print(a.aa) # 1
    print(hasattr(a,'bb')) #True
    
    a.aa = 'a' # 设置了aa == a
    setattr(a,'bb','bb') # 设置了bb == bb
    

     __getitem__和__setitem__

    对象使用[]时调用的方法, 这里主要说一下切片的原理. 

    关键字存在一个slice(), 其实这是一个类

    print(slice) # <class 'slice'>
    
    print(dir(slice))
    # ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', 
    # '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', 
    # '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 
    # '__str__', '__subclasshook__', 'indices', 'start', 'step', 'stop']
    

      其中indices方法可以根据序列长度修改切片信息,返回一个由起始位置,结束位置,步幅组成的元组.

    print(slice(0,10,2).indices(6)) # (0, 6, 2)
    print(slice(-3).indices(6)) # (0, 3, 1)
    

     来看一下我们切片时的操作吧

    class A:
        def __getitem__(self, item):
            print(item)
    
    a = A()
    a[1:2] # slice(1, 2, None)
    

      当使用切片时item就是slice对象

    我们这样就可以定义自己的序列类型的切片操作了

    class Start:
        def __init__(self,lis=None):
            if lis:
                if isinstance(lis,list):
                    self.lis = lis
    
                else:
                    raise TypeError('类型错误')
            else:
                self.lis = []
    
        def push(self,item):
            self.lis.append(item)
    
        def pull(self):
            return self.lis.pop()
    
        def __str__(self):
            return "%s(%s)"%(self.__class__.__name__,self.lis)
    
        def __getitem__(self, item):
            if isinstance(item,slice):
                return Start(self.lis[item])
            elif isinstance(item,int):
                return self.lis[item]
            else:
                raise TypeError("类型错误")
    
    a = Start([1,2,3,4,5])
    print(a[2]) # 3
    print(a[2:10]) # Start([3, 4, 5])

     __get__和__set__

    之前搜特殊方法的时候看到这两个是操作描述符时调用的方法,而描述符是什么呢? 实现了__get__, __set__或__delete__方法的类就是一个描述符

    描述符的用法是创建一个实例,作为另一个类的类属性.

    记得property()吧,特性其实也是描述符. 有一些受保护的属性,他的值需要做一些判断, 这时候我们用了property

    class Person:
        _age = None
        @property
        def age(self):
            return self._age
    
        @age.setter
        def age(self,age):
            if age>0 and age<100:
                self._age = age
    
    
    
    a = Person()
    a.age=10
    print(a.age)
    

    我们通过__get__和__set__也能够做到

    class Age:
        def __init__(self,age=None):
            self._age = age
    
        def __get__(self, instance, owner):
            print(instance,owner) # <__main__.Person object at 0x0000024396465470> <class '__main__.Person'>
            return self._age
    
        def __set__(self, instance, value):
            print(instance, value) # <__main__.Person object at 0x000002B109AB2160> 10
            if value>0 and value<100:
                self._age = value
    
            else :
                raise TypeError()
    
    
    class Person:
        age = Age()
    
    a = Person()
    print(a.age)
    a.age = 10
    b = Person()
    print(b.age) # 10
    

    干的漂亮b的age也变成了10,为什么? 这就是覆盖型描述符和非覆盖描述符

    实现__set__方法的描述符称为覆盖描述符,实现此方法会覆盖对实例属性的赋值操作. 之前我们给实例属性赋值时是在实例的名称空间内

    class Person:
        age = None
    
    a = Person()
    a.age = 10
    print(a.age) # 10 
    print(Person.age) # None
    

    再看一下上个例子

    class Age:
         ...
        def __str__(self):
            return self._age
    print(Person.age) # None <class '__main__.Person'> 10
    

    所以在描述符的例子中我们操作的是描述符实例,也就是类属性的值, 在描述符中是可以操作托管类实例的instance参数就是托管类实例. 通过instance和__dict__我们就可以将值存储在托管类实例的内存空间内了

    class Age:
    
        def __get__(self, instance, owner):
            print(instance,owner) # <__main__.Person object at 0x0000024396465470> <class '__main__.Person'>
            return instance.__dict__.get('age')
    
        def __set__(self, instance, value):
            print(instance, value) # <__main__.Person object at 0x000002B109AB2160> 10
            if value>0 and value<100:
                instance.__dict__['age'] = value
    
            else :
                raise TypeError()
    
    
    class Person:
        age = Age()
    
    
    
    a = Person()
    print(a.age)
    a.age = 10
    b = Person()
    print(b.age) # 10
    

    自己实现一个property

    class Property:
    
        def setattr(self, func):
            self.set = func
    
        @staticmethod
        def _set(*args):
            raise AttributeError("can't set attribute")
    
        def __init__(self,get,set=None,delter=None):
    
            self.get = get
            if set:
                self.set = set
            else:
                self.set = Property._set
            self.delter = delter
    
        def __get__(self, instance, owner):
            return self.get(instance)
    
        def __set__(self, instance, value):
            self.set(instance,value)
    
    
    
    class Person:
        _age = 0
        @Property
        def age(self):
            return self._age
        @age.setattr
        def getage(self,age):
            if age > 0 and age < 100:
                self._age = age
    
        # def getage(self):
        #         print('获取值')
        #         return self._age
        # age = Property(getage)
    a = Person()
    print(a.age)
    a.age = 10
    print(a.age)
    
    b = Person()
    print(b.age)
    

    描述符的分类:

    • 覆盖性描述符实现了__set__方法的描述符. 该方法会覆盖对实例属性的赋值操作, 用法建议例如只读属性可以在__set__中抛出异常
    • 没有实现__get__方法的描述符, 获取实例属性时会得到描述符实例, 用于验证可以只使用__set__方法
    • 没有实现__set__方法的描述符是非覆盖性描述符. 设置同名的实例属性描述符会被遮盖.

    值的注意的是,无论是不是覆盖性描述符,为类属性赋值都能覆盖描述符.

    事实上方法就是描述符,我们可以查看函数的方法

    class bb:
        def aa(self):
            pass
    # function
    
    b  = bb()
    print(b.aa) # <bound method bb.aa of <__main__.bb object at 0x000001EBE99853C8>>
    print(bb.aa) # <function bb.aa at 0x000001EBF89C8B70>
    print(dir(bb.aa))
    # ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', 
    # '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', 
    # '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', 
    # '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', 
    # '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 
    # '__str__', '__subclasshook__']
    

      我们可以看到函数实现了__get__方法, 通过instance来判断是否是由对象来调用的, 通过托管类访问时返回的是自身. 通过托管实例访问时会将instance绑定给函数的第一个参数, 类似partial

    from functools import partial
    class Func:
        def __get__(self, instance, owner):
            if not instance:
                return self
            else:
                return partial(self, instance)
    
        def __call__(self, *args, **kwargs):
            print(11111)
    
    class A:
        func = Func()
    
    print(A.func)
    a = A()
    print(a.func)

     __enter__ 和 __exit__

    上下文协议的两个方法, with后面的语句被求值后,返回对象的 __enter__() 方法被调用,这个方法的返回值将被赋值给as后面的变量。

    也就是说with语句后表达式的结果必须是实现了上下文协议的类的对象. 并且__enter__方法的返回值会被赋给as后的变量

    class A:
        def __enter__(self):
            print('进来了')
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('出去了')
    
        def b(self):
            print('执行了')
    
    with A() as a:
        a.b()
    

      在Flask中的AppContext类中可以看到类似的用法

    __exit__方法中有三个参数, 当一切正常时, 这三个参数都是None, 有异常抛出时三个参数分别是异常类, 异常实例, 以及traceback对象.

    class A:
        def __enter__(self):
            print('进来了')
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print(1,exc_type)
            print(2,exc_val)
            print(3,exc_tb)
            print('出去了')
    
        def b(self):
            print('执行了')
    
    with A() as a:
        a.b()
        raise TypeError('我错啦')
    
    # 执行如下
    进来了
    Traceback (most recent call last):
    执行了
      File "D:/python练习/yian2/shihui/tests.py", line 181, in <module>
    1 <class 'TypeError'>
        raise TypeError('我错啦')
    2 我错啦
    TypeError: 我错啦
    3 <traceback object at 0x0000022B387DFCC8>
    出去了
    

      这里也可以控制跳过某一个异常,只需要__exit__方法返回True即可, 修改__exit__方法如下

        def __exit__(self, exc_type, exc_val, exc_tb):
            print(1,exc_type)
            print(2,exc_val)
            print(3,exc_tb)
            print('出去了')
            if isinstance(exc_val,TypeError):
                return True
    
    # 
    进来了
    执行了
    1 <class 'TypeError'>
    2 我错啦
    3 <traceback object at 0x000001A0A715FCC8>
    出去了
    

      如果with语句块中跑出了多个异常??修改with语句如下

    with A() as a:
        a.b()
        raise TypeError('我错啦')
        print('我错没错')
        raise TypeError('我没有错')
    
    # 
    进来了
    执行了
    1 <class 'TypeError'>
    2 我错啦
    3 <traceback object at 0x000002A215D7FCC8>
    出去了
    

      可见with语句遇到一个没有被捕获的异常时便会退出

      

  • 相关阅读:
    iOS遍历程序内某个文件夹下所有文件的属性
    CATransition 转场动画
    Xcode安装的推送证书所在目录
    UIMenuController 实现长按显示自定义菜单功能
    ios调用第三方程序打开文件,以及第三方调用自己的APP打开文件
    购物车界面,不同section,点击增减物品,确定取消选中的逻辑判断
    iOS UINavigationController
    iOS9 URL Schme 白名单
    iOS9 HTTPS
    iOS9 后台定位
  • 原文地址:https://www.cnblogs.com/wwg945/p/9495297.html
Copyright © 2011-2022 走看看