Python魔术方法之描述器

摘要

七天假期最后一天,假期账户余额已严重不足,那就总结下这两天的学习内容吧。本文主要讲解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描述器有了一个全新的认识,加油吧骚年!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,546评论 6 13
  • 1.1. 摘要 定义描述器, 总结描述器协议,并展示描述器是怎么被调用的。展示一个自定义的描述器和包括函数,属性(...
    mutex73阅读 422评论 0 2
  • Fluent Python Metaprogramming 部分的笔记, 在加上了其他杂七杂八的东西 Dynami...
    SkyDavid阅读 885评论 0 0
  • 你都这样说了我能有什么办法,就这样吧,如果我不累那就继续,如果我累了,那就各自安好
    薇薇安1802785阅读 90评论 0 0
  • 如果一生中你只打算折腾vim一次或者几次,那么认真读这篇文章就好了。没错,这就是.vimrc文件的Finnal版。...
    xhat阅读 5,945评论 1 6