资讯专栏INFORMATION COLUMN

PyTips 0x14 - Python 描述符

since1986 / 1109人阅读

摘要:项目地址本篇主要关于三个常用内置方法,,在语言的设计中,通常的语法操作最终都会转化为方法调用,例如相当于中的描述符就是将对象属性的获取赋值以及删除等行为转换为方法调用的协议例如我们要获取一个对象的属性,可以通过的方式取得而通过的

项目地址:https://git.io/pytips

本篇主要关于三个常用内置方法:property()staticmethod()classmethod()

在 Python 语言的设计中,通常的语法操作最终都会转化为方法调用,例如:

a = 1
b = 2
print("a + b = {}".format(a+b))

# 相当于
print("a.__add__(b) = {}".format(a.__add__(b)))
a + b = 3
a.__add__(b) = 3

Python 中的描述符(Descriptor)就是将对象属性的获取、赋值以及删除等行为转换为方法调用的协议:

descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

例如我们要获取一个对象的属性,可以通过o.x的方式取得:

class Int:
    ctype = "Class::Int"
    def __init__(self, val):
        self._val = val
        
a = Int(1)
print(a.ctype)
Class::Int

而通过.的方式寻找属性的值实际上调用了object.__getattribute__(self, name)方法:

class Int:
    ctype = "Class::Int"
    def __init__(self, val):
        self._val = val
    def __getattribute__(self, name):
        print("? doesn"t want to give `{}" to you!".format(name))
        return "?"
a = Int(2)
print(a.ctype)
? doesn"t want to give `ctype" to you!
?

而这里的__getattribute__(self, name)方法实际上就是将.的属性获取方法转化为描述符协议定义的descr.__get__(self, key)

class Str:
    def __init__(self, val):
        self._val = val
    def __get__(self, name, ctype=None):
        print("You can __get__ anything from here!")
        return self._val
class Int:
    ctype = Str("Class::Int")
    def __init__(self, val):
        self._val = val
    def __getattribute__(self, name):
        return type(self).__dict__[name].__get__(None, type(self))
a = Int(2)
print(a.ctype)
You can __get__ anything from here!
Class::Int

这里的 a.ctype = (Int.__dict__["ctype"]).__get__(None, Int),即通过描述符的方式获取了 ctype 属性的值。同样的道理,你也可以通过 descr.__set__(self, obj, val) 设置属性的值:

class Str:
    def __init__(self, val):
        self._val = val
    def __get__(self, name, ctype=None):
        print("You can __get__ anything from here!")
        return self._val
    def __set__(self, name, val):
        print("You can __set__ anything to me!")
        self._val = val
class Int:
    ctype = Str("Class::Int")
    def __init__(self, val):
        self._val = val
a = Int(3)
print(a.ctype)
a.ctype = "Class::Float"
print(a.ctype)
You can __get__ anything from here!
Class::Int
You can __set__ anything to me!
You can __get__ anything from here!
Class::Float

将这些取值、赋值的操作转换为方法调用让我们有办法在做这些操作的过程中插入一些小动作,这么好用的东西自然是已加入豪华内置函数阵容,正是我们常见的

property()

classmethod()

staticmethod()

property

property(fget=None, fset=None, fdel=None, doc=None) 方法简化了上面的操作:

class Int:
    def __init__(self, val):
        self._val = val
        self._ctype = None
        
    def get_ctype(self):
        print("INFO: You can get `ctype`")
        return self._ctype
    def set_ctype(self, val):
        print("INFO: You"re setting `ctype` =", val)
        self._ctype=val
    ctype = property(fget=get_ctype, fset=set_ctype, doc="Property `ctype`")
    
a = Int(4)
print(a.ctype)
a.ctype = "Class::Int"
print(a.ctype)
INFO: You can get `ctype`
None
INFO: You"re setting `ctype` = Class::Int
INFO: You can get `ctype`
Class::Int

显然,更方便一些的用法是将 property 当做修饰器:

class Int:
    _ctype = None
    def __init__(self, val):
        self._val = val
    @property
    def ctype(self):
        print("INFO: You can get `ctype` from me!")
        return self._ctype
    @ctype.setter
    def ctype(self, val):
        print("INFO: You"re setting `ctype` =", val)
        self._ctype = val
a = Int(5)
print(a.ctype)
a.ctype = "Class::Int"
print(a.ctype)
INFO: You can get `ctype` from me!
None
INFO: You"re setting `ctype` = Class::Int
INFO: You can get `ctype` from me!
Class::Int

staticmethod & classmethod

顾名思义,property 是关于属性的全部操作,如果是要获取类中的方法,则需要用到 staticmethodclassmethod。顾名思义,staticmethod 将方法变成静态方法,即类和实例都可以访问,如果不用 staticmethod 我们可以用下面这种别扭的方法实现:

class Int:
    def __init__(self, val):
        self._val = val
    def _get_ctype(self=None):
        print("INFO: You can get `ctype` from here!")
        return "Class::Int"
    
    @staticmethod
    def get_ctype():
        print("INFO: You can get `ctype` from here!")
        return "Class::StaticInt"       
    
a = Int(6)
print(a._get_ctype())
print(Int._get_ctype())

print(a.get_ctype())
print(Int.get_ctype())
INFO: You can get `ctype` from here!
Class::Int
INFO: You can get `ctype` from here!
Class::Int
INFO: You can get `ctype` from here!
Class::StaticInt
INFO: You can get `ctype` from here!
Class::StaticInt

可以看到,静态方法与类和实例无关,也就不再(不能)需要 self 关键词;与之相反,当我们需要在方法中保留类(而非实例)的引用时,则需要用 classmethod

class Int:
    _ctype = ""
    def __init__(self, val):
        self._val = val
        
    @classmethod
    def set_ctype(klass, t):
        klass._ctype = t
        return "{}.ctype = {}".format(klass.__name__, t)
a = Int(7)
print(a.set_ctype("Class::Int"))
print(Int.set_ctype("Class::Float"))
b = Int(8)
print(b._ctype)
Int.ctype = Class::Int
Int.ctype = Class::Float
Class::Float
总结

Python 的描述符给出一种通过方法调用来实现属性(方法)获取、赋值等操作的规则,通过这一规则可以方便我们深入程序内部并实施操控,因此 property/staticmethod/classmethod 在 Python 是通过底层(如 CPython 中的 C)实现的,如果想要进一步深入了解其实现原理,可以访问参考链接的教程,其中包括了这三个内置方法的 Python 实现版本,我也把它们 copy 过来方便查看。


欢迎关注公众号 PyHub 每日推送

参考

Descriptor HowTo Guide

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can"t set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can"t delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f
        
    def __get__(self, obj, objtype=None):
        return self.f

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/45427.html

相关文章

  • PyTips 0x02 - Python 中的函数式编程

    摘要:项目地址中的函数式编程函数式编程英语或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。 项目地址:https://git.io/pytips Python 中的函数式编程 函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且...

    FrozenMap 评论0 收藏0
  • PyTips 0x15 - Python `__future__` 模块

    摘要:模块的导入一定要放在最上方,也就是在所有其它模块之前导入。最后一列是每个新特性所对应的及简单描述。相对导入则可以使用为标记导入相对目录中的模块,具体可以参考这篇文章导入模块的几种姿势。 项目地址:https://git.io/pytips 我们经常从一些组织良好的 Python 项目中看到 __future__ 的身影,例如: from __future__ import absolu...

    klinson 评论0 收藏0
  • PyTips 0x08 - Python 字节与字节数组

    摘要:回到对字节和字节数组的定义为了用计算机可以理解的数字描述人类使用的字符,我们需要一张数字与字符对应的表。由于和字符串一样是序列类型,字节和字节数组可用的方法也类似,这里就不一一列举了。 项目地址:https://git.io/pytips 0x07 中介绍了 Python 中的字符串类型,字符串类型是对人类友好的符号,但计算机只认识一种符号,那就是二进制(binary)数,或者说是数字...

    Leo_chen 评论0 收藏0
  • PyTips 0x0a - Python串的格式化

    摘要:项目地址相信很多人在格式化字符串的时候都用的语法,提出一种更先进的格式化方法并成为的标准用来替换旧的格式化语法,从开始已经实现了这一方法其它解释器未考证。 项目地址:https://git.io/pytips 相信很多人在格式化字符串的时候都用%s % v的语法,PEP 3101 提出一种更先进的格式化方法 str.format() 并成为 Python 3 的标准用来替换旧的 %s ...

    luqiuwen 评论0 收藏0
  • PyTips 0x 12 - Python 线程与协程(1)

    摘要:中关于线程的标准库是,之前在版本中的在之后更名为,无论是还是都应该尽量避免使用较为底层的而应该使用。而与线程相比,协程尤其是结合事件循环无论在编程模型还是语法上,看起来都是非常友好的单线程同步过程。 项目地址:https://git.io/pytips 要说到线程(Thread)与协程(Coroutine)似乎总是需要从并行(Parallelism)与并发(Concurrency)谈起...

    el09xccxy 评论0 收藏0

发表评论

0条评论

since1986

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<