項目地址: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
是關於屬性的所有操做,若是是要獲取類中的方法,則須要用到 staticmethod
和 classmethod
。顧名思義,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 每日推送
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