Python魔术方法之描述器

作者: 城南道 | 来源:发表于2016-10-07 17:21 被阅读298次

摘要

七天假期最后一天,假期账户余额已严重不足,那就总结下这两天的学习内容吧。本文主要讲解Python中另一种高级特性描述器,学习如何自定义描述器,和几个内置的描述器,包括函数、property、静态方法和类方法。并运用我们所学的知识,用纯Python实现上述方法。最后介绍描述器的几个运用场景,并给出真实的例子说明。

定义

描述器单独出现是无意义的,它总是和其他类的类属性一起出现,事实上,描述器描述了类属性的访问、赋值和删除行为,所以它被叫做描述器

描述器是怎么工作的

描述器仅仅是一个对象,跟其他对象相比并没有内在的特殊之处。就像Python其他强大特性一样,它们非常的简单。想要深入了解描述器协议,有三个条件需要搞清楚:

  • 你有一个新式的类
  • 它有类属性
  • 这个类属性有描述器的几个方法
    上面所说的描述器协议,包括以下几个方法:
  • __get__(self, instance, ower) --> value
    获得对象的属性obj.description相当于description.__get__(obj, OwerClass)
    获得类的属性OwnerClass.descriptor相当于descriptor.__get__(None, OwnerClass)
  • __set__(self, instance, value) --> None
    给对象赋值obj.descriptor = 5相当于descriptor.__set__(obj, 5)
  • __delete__(self, instance)删除对象属性
    del obj.description 相当于description.__delete__(obj)
    这边有个易混淆的地方:描述器属性被触发是因为对象的属性(只有描述器对象是其他类的类属性时候才起作用),但是在上述三个方法中self是描述器本身,而不是对象

简单的描述器

class Int:
    def __init__(self, name):
        print('Int init')
        self.name = name

    def __get__(self, instance, cls):
        print('access')
        print('{!r} {!r} {!r}'.format(self, instance, cls))
        if instance is None:
            return self
        print(instance.__dict__)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set')
        print('{!r} {!r} {!r}'.format(self, instance, value))
        instance.__dict__[self.name] = value
        print(instance.__dict__)


    def __delete__(self, instance):
        pass


class A:
    x = Int('x')
    print(x)

    def __init__(self, x):
        print('A init')
        self.x = x

# out:    在定义类A的时候就会生出类属性x
Int init
<__main__.Int object at 0x7fcb38f916a0>

a = A(4)
# out:
A init
set
<__main__.Int object at 0x7fcb38f916a0> <__main__.A object at 0x7fcb38ff5a20> 4
{'x': 4}

a.x
# out:
access
<__main__.Int object at 0x7fcb3874aeb8> <__main__.A object at 0x7fcb386b12e8> <class '__main__.A'>
{'x': 4}
4

描述器协议非常的简单,用途十分广泛,以至于他们被打包成独立的函数。像属性(property)、静态方法和类方法都是基于描述器协议的。

属性(property)

下面是一个纯Python实现的property

class Property:
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel

    def __get__(self, instance, cls):
        print('get')
        print(instance)
        print(self.fget)
        print(cls)
        if instance is None:
            return self
        if not callable(self.fget):
            raise AttributeError()
        print(self.fget(instance))
        return self.fget(instance)

    def __set__(self, instance, value):
        print('set')
        if not callable(self.fset):
            raise AttributeError()
        print(self.fset)
        self.fset(instance, value)

    def __delete__(self, instance):
        print('delete')
        if not callable(self.fdel):
            raise AttributeError()
        self.fdel(instance)

    def setter(self, fset):
        print('setter')
        print(fset)
        return Property(self.fget, fset, self.fdel)
    
    def deleter(self, fdel):
        return Property(self.fget, self.fset, fdel)
   

class A:
    def __init__(self, x):
        self.__x = x

    @Property
    def x(self):     # x = Property(A.x)
        return self.__x

    @x.setter  # x = x.setter(A.x) = Property(x.fget, A.x, x.fdel)
    def x(self, value):
        self.__x = value

# out:
setter
<function A.x at 0x7fcb38f9fc80>

a = A(10)

a.x
# out:
get
<__main__.A object at 0x7fcb387439b0>
<function A.x at 0x7fcb3873bd08>
<class '__main__.A'>
10

a.x = 11
# out:
set
<function A.x at 0x7fcb3873bc80>

上述代码中我加了很多打印,能够帮助大家理解,如果觉得繁琐,可以删除上述打印。

  • Property对象是描述器
  • Property.setterProperty.deleter都是装饰器,他们返回的是Property()对象,不同的是@Property设置的是fgetsetterdeleter分别设置fsetfdel

类方法

纯Python实现类方法,代码如下:

from functools import wraps, partial

class Classmethod:
    def __init__(self, method):
        wraps(method)(self)

    def __get__(self, instance, cls):
        return partial(self.__wrapped__, cls)

class C:
    @Classmethod
    def method(cls):
        print(cls)

    def method2(self, x):
        print(cls)
        print(x)

C.method()
# out:
class method
<class '__main__.C'>

上述代码完美的实现了类方法装饰器,有一个函数wraps()需要着重讲解一下
wraps(fn)(g)fn的一些属性(比如__module__, __name__, __qualname__, __doc__,__annotations__),复制给g(主要是更新g__dict__),g就有一个属性叫做__wrapped__g.__wrapped__就等价于fn

描述器的一个应用

用描述器做类型检查

from inspect import signature

class Typed:
    def __init__(self, name, required_type):
        self.name = name
        self.required_type = required_type

    def __get__(self, instance, cls):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.required_type):
            raise TypeError('{} required {}'.format(self.name, self.required_type))
        instance.__dict__[self.name] = value

def typeassert(cls):
    sig = signature(cls)
    for k, v in sig.parameters.items():
        setattr(cls, k, Typed(k, v.annotation))
    return cls

@typeassert
class Person:
    def __init__(self, name: str, age: int):  # Python3.5最新类型提示
        self.name = name
        self.age = age

p = Person(18, 'xus')
# out:
TypeError: name required <class 'str'>

结语

希望大家对Python描述器有了一个全新的认识,加油吧骚年!

相关文章

网友评论

    本文标题:Python魔术方法之描述器

    本文链接:https://www.haomeiwen.com/subject/rxiyyttx.html