元类和自省
自省和反射
自省 (introspection): 可以在运行时获得对象的类型信息
type introspection is the ability of a program to examine the type or properties of an object at runtime
例子; python 中最常见的 dir
def __init__(self, val) -> None:
self.x = val
def bar(self):
return self.x
...
>>> dir(Foo(5))
# ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
# '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__',
# '__repr__', '__setattr__', '__str__', '__weakref__', 'bar', 'x']
反射 (reflection) 是包括自省的,指检查,自省以及修改自身结构和行为的能力。
Introspection should not be confused with reflection, which goes a step further and is the ability for a program to manipulate the values, meta-data, properties and/or functions of an object at runtime.
reflection is the ability of a process to examine, introspect, and modify its own structure and behavior.
例子:
# Without reflection
obj = Foo()
obj.hello()
# With reflection
obj = globals()['Foo']()
getattr(obj, 'hello')()
# With eval
eval('Foo().hello()')
元类是什么
python 中,所有的数据类型都可以视为对象,当然也可以自定义对象。
类也是一种对象,类同时拥有创建对象(类实例)的能力。
class myObject(object):
pass
item = myObject()
print(myObject) # <class '__main__.myObject'>
print(item) # <__main__.myObject object at 0x10a936080>
当使用 class 关键字时,Python 解释器自动创建这个对象。但就和Python 中的大多数事情一样,Python 仍然提供给了手动处理的方法:
type 除了可以告诉你一个对象的类型是什么,还能动态的创建类。
print(type(1)) # <class 'int'>
print(type('1')) # <class 'str'>
print(type(item)) # <class '__main__.myObject'>
print(type(myObject)) # <class 'type'>
# type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
myMetaClass = type('myMetaClass', (), {})
print(myMetaClass) # <class '__main__.myMetaClass'>
print(myMetaClass()) # <__main__.myMetaClass object at 0x10a9367f0>
元类就是用来创建类的“东西”,在 Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字 class 时 Python 在幕后做的事情,而这就是通过元类来实现的。
元类的使用
声明元类
class Spam(metaclass=Meta): # 3.0 and later
class Spam(Eggs, metaclass=Meta): # Other supers okay
class spam(object): # 2.6 version(only)
__metaclass__ = Meta
自定义元类
使用简单的工厂函数
任何可调用对象都可以用作一个元类,只要它接受传递的参数并且返回与目标类兼容的一个对象
# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
'''返回一个类对象,将属性都转为大写形式'''
# 选择所有不以'__'开头的属性
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
class Foo(metaclass=upper_attr):
bar = 'bip'
f = Foo()
print(f.BAR) # bip
从技术上讲,实例属性查找通常只是搜索实例及其所有类的dict字典;元类不包含在实例查找中。
用常规类重载类创建调用
class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 这种方式其实不是OOP。我们直接调用了type,而且我们没有改写父类的__new__方法。
# return type(future_class_name, future_class_parents, uppercase_attr)
# 复用type.__new__方法
# return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
# 如果使用super方法的话,我们还可以使它变得更清晰一些
return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
class Foo(metaclass=UpperAttrMetaclass):
bar = 'bip'
f = Foo()
print(f.BAR) # bip
元类其实只做了三件事:
- 拦截类的创建
- 修改类
- 返回修改之后的类
为什么用类而不是函数
由于 metaclass 可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?这里有好几个原因:
-
意图会更加清晰。当你读到UpperAttrMetaclass(type)时,你知道接下来要发生什么。
-
使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。
-
可以把代码组织的更好。使用元类的时候通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
-
可以使用 new, init 以及 call 这样的特殊方法。它们能帮你处理不同的任务。
为什么要使用元类
元类的主要用途是创建API。一个典型的例子是Django ORM。
对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:
- Monkey patching
- class decorators
以下是一个计数功能分别用装饰器和元类实现的例子:
from types import FunctionType
def tracer(func):
calls = 0
def on_call(*args, **kwargs):
nonlocal calls
calls += 1
print('call %s to %s' % (calls, func.__name__))
return func(*args, **kwargs)
return on_call
class Person1:
@tracer
def __init__(self, name, pay):
self.name = name
self.pay = pay
@tracer
def giveRaise(self, percent):
self.pay *= (1.0 + percent)
@tracer
def lastName(self):
return self.name.split()[-1]
bob = Person1('Bob Smith', 50000)
sue = Person1('Sue Jones', 100000)
print(bob.name, sue.name)
sue.giveRaise(.10)
print(sue.pay)
print(bob.lastName(), sue.lastName())
# call 1 to __init__
# call 2 to __init__
# Bob Smith Sue Jones
# call 1 to giveRaise
# 110000.00000000001
# call 1 to lastName
# call 2 to lastName
# Smith Jones
class MetaTrace(type):
def __new__(meta, classname, supers, classdict):
for attr, attrval in classdict.items():
if type(attrval) is FunctionType:
classdict[attr] = tracer(attrval)
return type.__new__(meta, classname, supers, classdict)
class Person2(metaclass=MetaTrace):
def __init__(self, name, pay):
self.name = name
self.pay = pay
def giveRaise(self, percent):
self.pay *= (1.0 + percent)
def lastName(self):
return self.name.split()[-1]
bob = Person2('Bob Smith', 50000)
sue = Person2('Sue Jones', 100000)
print(bob.name, sue.name)
sue.giveRaise(.10)
print(sue.pay)
print(bob.lastName(), sue.lastName())
# call 1 to __init__
# call 2 to __init__
# Bob Smith Sue Jones
# call 1 to giveRaise
# 110000.00000000001
# call 1 to lastName
# call 2 to lastName
# Smith Jones
单例的例子:
# 单例模式
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
# Example
class Spam(metaclass=Singleton):
def __init__(self):
print('Creating Spam')
# 非元类单例
class _Spam:
def __init__(self):
print('Creating Spam')
_spam_instance = None
def Spam():
global _spam_instance
if _spam_instance is not None:
return _spam_instance
else:
_spam_instance = _Spam()
return _spam_instance
缓存例子:
import weakref
class Cached(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__cache = weakref.WeakValueDictionary()
def __call__(self, *args):
if args in self.__cache:
return self.__cache[args]
else:
obj = super().__call__(*args)
self.__cache[args] = obj
return obj
# Example
class Spam(metaclass=Cached):
def __init__(self, name):
print('Creating Spam({!r})'.format(name))
self.name = name