七夕节写那些结伴而行的特殊方法

__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语句遇到一个没有被捕获的异常时便会退出

  

posted @ 2018-08-17 21:08  瓜田月夜  阅读(178)  评论(0编辑  收藏  举报