PyTips 0x14 - Python 描述符

項目地址:https://git.io/pytipshtml

本篇主要關於三個經常使用內置方法:property()staticmethod()classmethod()python

在 Python 語言的設計中,一般的語法操做最終都會轉化爲方法調用,例如:git

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)就是將對象屬性的獲取、賦值以及刪除等行爲轉換爲方法調用的協議:ide

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)方法:ui

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)spa

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

將這些取值、賦值的操做轉換爲方法調用讓咱們有辦法在作這些操做的過程當中插入一些小動做,這麼好用的東西天然是已加入豪華內置函數陣容,正是咱們常見的code

  • property()orm

  • 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 每日推送

歡迎關注公衆號 PyHub!

參考

  1. 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
相關文章
相關標籤/搜索