习惯了Ruby,总希望能够在其他语言里面找到同样的身影。比如,通过method_missing
去处理没有定义的方法,或者用define_method
,class_eval
等去动态定义方法,抑或者用included
, prepended
, extended
这些方法增加方法。
但是在Python中,我基本没有看到included
这样的操作,甚至对于类这样的概念,用的都不是很频繁。我有时候都怀疑Python里面有没有元编程,虽然我知道django
里面肯定有元编程,类似的ORM包里面肯定也有。
无论如何,的确需要看看Python里面的元编程是怎么样的。
动态属性
在Python中,你可以通过覆写__getattr__
来实现虚拟属性。比如,访问某个json节点,可以通过__getattr__
来访问json的内容。
特性
通过设置@property
装饰器来实现属性的读取和写入。
class Class:
data = 'the class data attr'
@property
def prop(self):
return 'the prop value'
如果实例中没有对应的属性的时候,会试图去读取类属性。
# check attr
obj = Class()
print(vars(obj)) # => {}
print(obj.__dict__) # => {}
print(obj.data) # => the class data attr
obj.data = 'bar'
print(vars(obj)) # => {'data': 'bar'}
print(obj.__dict__) # => {'data': 'bar'}
print(obj.data) # => bar
print(Class.data) # => the class data attr
而且,特性(类属性)会覆盖实例属性。所以obj.attr
会先从obj.__class__
中寻找名为attr
的特性,找不到再从obj
实例中寻找。
# check for property
print(Class.prop) # => <property object at 0x10bf71368>
print(obj.prop) # => the prop value
# obj.prop = 'foo' # error raise here
# 特性依旧覆盖实例属性
obj.__dict__['prop'] = 'foo'
print(obj.prop) # => the prop value
# remove property 则返回实例属性
Class.prop = 'abc'
print(obj.prop) # => foo
# add property back
# instance attr is overriden by Class property
print(obj.data) # => bar
Class.data = property(lambda self: 'the "data" prop value')
print(obj.data) # => the "data" prop value
# instance attr is back
del Class.data
print(obj.data) # => bar
特性工厂
可以通过定义一个quantity
函数,返回property
对象。这样也可以实现@property
装饰器同样的效果。
def quantity(storage_name):
def qty_getter(instance):
return instance.__dict__[storage_name]
def qty_setter(instance, value):
if value > 0:
instance.__dict__[storage_name] = value
else:
raise ValueError('value must be > 0')
return property(qty_getter, qty_setter)
class LineItem:
weight = quantity('weight')
price = quantity('price')
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
描述符
描述符是指实现了__get__
, __set__
和__delete__
的类。上面的property类实现了完整的描述符协议。下面是描述符的实现,等效于特性工厂的实现。
class Quantity:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix, index)
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value)
else:
raise ValueError('value must be > 0')
class LineItem:
weight = Quantity()
price = Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
- 覆盖性描述符拥有
__set__
方法- 有
__get__
和__set__
的描述符,和特性一样,obj的读取和写入都会调用__get__
和__set__
方法。 - 没有
__get__
方法,通过实例读取描述符就会返回描述符本身。如果实例有同名属性,则返回该属性。设置还是会有__set__
接手。
- 有
- 非覆盖性描述符
- 只有
__get__
方法,实例属性还是会覆盖__get__
方法。
- 只有
所有的方法都是非覆盖性描述符。
元类
晃眼一看,还以为是Ruby中singleton_class
,但实际上不一样。
Singleton
类的基类是type
,Spam
中有metaclass=Singleton
。这样,在导入的时候,就会触发Singleton
的__init__
方法。这里会设置Spam._instance = None
。当第一次调用Spam()
的时候,就会调用__call__
方法,这个时候self._instance
是None
,就新生成一个object。第二次调用的时候,就返回上一次生成的实例。
class Singleton(type):
def __init__(self, *args, **kwargs):
self._instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self._instance is None:
self._instance = super().__call__(*args, **kwargs)
return self._instance
else:
return self._instance
class Spam(metaclass=Singleton):
def __init__(self):
print("Spam!!!")
但是,书上并不建议使用元类,除非你在创建某个框架。