Classes and Metaprograming
Python中的objects都有自己的类型,class
也不例外,在Python中,class是first-class citizen。即class
也是一个object,它有自己的类型,也就是说class
有所谓的metaclass,具有meta programing的能力。
class MyClass:
pass
上面代码中,python会创建一个MyClass
的object,它的类型是type
,因此我们可以用它来定义其他对象。既然MyClass
是个object,则它有自己的method,比如
MyClass.__name__ # 'MyClass'
Attributes
每个class
object可以关联attribute,我们用getattr
和setattr
来操作attribute,也可以用.
class MyClass:
version = 3.6
def say_hello(self):
print("hello")
setattr(MyClass, 'version', 3.8)
MyClass.version = '3.8'
getattr(MyClass, 'version', 'N/A')
# this add a new attribute to MyClass
setattr(MyClass, 'language', 'python')
# or
MyClass.language = 'python'
注意,此时我们还是在讨论MyClass
作为object本身而不是作为type,我们上面的操作是对MyClass
这个object本身状态的修改,因此也可以看做meta programming。
实际上在Python中,class object和class instance是完全分开的
Python中,class
对象的的状态都保存在一个dictionary里,比如
MyClass.__dict__
mappingproxy({
'__module__': '__main__', # namespace
'version': 3.6,
'language': 'python',
'say_hello': <function __main__.Program.say_hello()>,
'__dict__': <attribute '__dict__' of 'MyClass' objects>,
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>,
'__doc__': None})
可以将mappingproxy
看成一个read-only的map,我们是无法修改它的,因此想要修改class的state,只能通过setattr
或.
来完成。
我们还可以delete attribute
delattr(MyClass, 'version')
del MyClass.version
接下来我们来看看所谓的instance attribute,还是看下面的代码
my_obj = MyClass()
my_obj.__dict__ # {}
my_obj.language = 'java'
my_obj.__dict__ #{ 'language' : 'java' }
MyClass.language # 'Python'
当我们创建了一个my_obj
时,它的namespace和MyClass
的namespace是不同的,因此,他们的__dict__
也不同。对于my_obj
,当它调用.language
的时候,python会首先从它自己的__dict__
中查找,发现没有,接着才会继续向上查找它class
的attribute,即MyClass.language
。找到后,当my_obj
需要需改其值时,它并不会修改MyClass.language
而是会创建一个instance attribute,即language
会出现在自己的__dict__
中。这是一种很巧妙的设计实现了attribute的默认值。
而对于attribute method来说,情况会有些不同,上面例子中,一个method包含下面两部分
__self__
表示这method被bound到哪个object__func__
表示实际的定义在class上的function
当obj.method(args)
被调用时,Python实际调用的是
mothod.__func__(method.__self__, args)
因此,当调用my_obj.say_hello()
时,实际上调用的时MyClass.say_hello(my_obj)
。
我们可以用MethodType
来给MyClass
的object动态增加一个method
from types import MethodType
obj = MyClass()
obj.foo = MethodType(lambda self: f'foo is called', obj)
obj.foo()
此时,foo
会出现在obj
的namespace中(obj.__dict__
),但是却不会出现在MyClass
的namespace中。即foo
只存在于obj
上,而不存在于MyClass
这是上
Class Body Scope
Python中的class
有自己的scope,所有定义在class中的attributes都属于这个scope,他们都会出现在MyClass.__dict__
中,但是成员函数需要注意,它们的代码在class中,但实际的scope却是和所在module是一致的
class MyClass:
name = 'myclass'
def func(self):
print(name) # wrong, the outter scope doesn't have a name object
Properties
property
会自动给attribute添加getter和setter,使用方式如下
class MyClass:
def __init__(self, language):
self._language = language
def set_language(self, value):
self._language = value
def get_language(self):
return self._language
language = property(fget=get_language, fset=set_language)
m = MyClass('Python') # m.__dict__ -> {'_language' : 'Python'}
m.language = 'Java' # (1)
上面property
实际上是一个class,当(1)
执行时,python首先会从__dict__
中寻找language
,发现没有,接着会从MyClass
的__dict__
中寻找,发现这样一条记录
'language': <property object at 0x7f96c0093770>,
此时,lauguage
是一个property,于是Python会接着找他的getter和setter方法,从而取得想要的结果。
实际上,我们可以将property
做为一个decorator。property
的第一个参数是getter,第二个参数是setter,第三个是deleter。
def language(self):
return self._language
def set_language(self, value):
self._language = value
language = propery(lauguage)
@property
def language(self):
return self._language
language = language.setter(set_language)
@language.setter
def language(self, value):
self._language = value
有了property,虽然我们不能彻底的hide attribute,但是可以像objective-c的property一样,可以有readonly的属性以及执行lazy evaludation
class Circle:
def __init__(self, r):
self._r = r
self._area = None
@property
def radius(self):
return self._r
@radius.setter
def radius(self, r):
if r < 0:
return
self._r = r
self._area = None
@property
def area(self):
if self._area is None:
self._area = math.pi * (self._r ** 2)
return self._area
上面代码中,我们没有为area
指定setter,这从一定程度上可以保护area
不被外部修改。此外,getter中我们实现了caching,不需要每次都进行计算area
。
Descriptior
如果一个类的propery过多会产生大量的重复代码