本篇主要关于三个常用内置方法:
property(),staticmethod(),classmethod()
在 Python 语言的设计中,通常的语法操作最终都会转化为方法调用,例如:
a = 1
b = 2
print("a + b = {}".format(a+b))
# 相当于
print("a.__add__(b) = {}".format(a.__add__(b)))
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):
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 是关于属性的全部操作,如果是要获取类中的方法,则需要用到
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 过来方便查看。
参考
-
Descriptor HowTo Guide