[译]什么是元类metaclass?
原文地址:what is metaclass in Python?
我的简书地址::nummy
类即对象
在理解元类之前,需要先掌握Python中的类,Python中类的概念与SmallTalk中类的概念相似。
在大多数语言中,类是用来描述如何创建对象的代码段,这在Python中也是成立的:
class ObjectCreator(object):
... pass
...
my_object = ObjectCreator()
print(my_object)
Python中,类其实也是对象。当我们使用关键字class的时候,Python会执行这段代码,然后生成一个对象。下面的代码在内存中创建一个对象ObjectCreator:
class ObjectCreator(object):
... pass
...
当一个对象具有创建对象的能力时,就称该对象为类。
所以类本质上还是一个对象,因此它具有以下属性:
可以将它赋值给其它变量
可以对它进行复制
可以给它添加属性
可以将它传递给函数作为参数
例如:
print(ObjectCreator) # you can print a class because it's an object
def echo(o):
... print(o)
...
echo(ObjectCreator) # you can pass a class as a parameter
print(hasattr(ObjectCreator, 'new_attribute'))
False
ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
print(hasattr(ObjectCreator, 'new_attribute'))
True
print(ObjectCreator.new_attribute)
foo
ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
print(ObjectCreatorMirror.new_attribute)
foo
print(ObjectCreatorMirror())
动态创建类
既然类就是对象,那我们就可以像创建其他对象一样动态创建类。
首先,在函数中使用class创建一个类:
def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
MyClass = choose_class('foo')
print(MyClass) # the function returns a class, not an instance
print(MyClass()) # you can create an object from this class
但是上面的例子也称不上是完全动态的创建类,因为我们还需要在其中编写整个类的代码。
既然类就是对象,那么它们肯定是通过某个东西来创建的。当使用class关键字的时候,Python会自动创建类,Python也提供了方法让我们手动来创建类。
还记得type()函数吗?这个函数可以获取对象的类型。
print(type(1))
print(type("1"))
print(type(ObjectCreator))
print(type(ObjectCreator()))
type还有另外一个功能,那就是创建类。type使用类的相关描述作为参数,然后返回一个类。
type创建类的语法如下:
type(类名,基类元组(可以为空,用于继承), 包含属性或函数的字典)
例如:
class MyShinyClass(object):
... pass
上面的类可以使用下面的方法手动创建:
MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
print(MyShinyClass)
print(MyShinyClass()) # create an instance with the class
type也接收一个字典参数来定义类中的属性:
class Foo(object):
... bar = True
等价于
Foo = type('Foo', (), {'bar':True})
通过type创建的类使用方式跟普通类一样:
print(Foo)
print(Foo.bar)
True
f = Foo()
print(f)
print(f.bar)
True
当然也可以继承:
class FooChild(Foo):
... pass
等价于:
FooChild = type('FooChild', (Foo,), {})
print(FooChild)
print(FooChild.bar) # bar is inherited from Foo
True
最后,我们可能还想给类添加方法,可以先定义一个函数,然后将它以属性的方式赋予给类。
def echo_bar(self):
... print(self.bar)
...
FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
hasattr(Foo, 'echo_bar')
False
hasattr(FooChild, 'echo_bar')
True
my_foo = FooChild()
my_foo.echo_bar()
True
而且,我们还可以在动态创建类之后,给类添加更多的方法和属性:
def echo_bar_more(self):
... print('yet another method')
...
FooChild.echo_bar_more = echo_bar_more
hasattr(FooChild, 'echo_bar_more')
True什么是元类?
通常,我们定义类来创建对象,但是现在我们知道类也是对象。那么是通过什么来创建类呢?答案就是元类。你可以想象关系如下:
MyClass = MetaClass()
MyObject = MyClass()
你已经知道使用type可以创建类:
MyClass = type('MyClass', (), {})
那是因为type函数实际上就是一个元类,Python使用type作为元类来创建所有的类。
通过检查class属性,我们可以知道,其实Python中任何数据类型都是对象,包括整型、字符串、函数以及类,它们都是对象。它们都是从类中创建的。
age = 35
age.class
name = 'bob'
name.class
def foo(): pass
foo.class
class Bar(object): pass
b = Bar()
b.class
那么class的class是什么呢?
age.class.class
name.class.class
foo.class.class
b.class.class
所以类其实就是通过元类来创建的,你可以将元类称之为类工厂。
type是内置的元类,Python默认使用它来创建类。当然,我们也可以定义属于我们自己的元类。
metaclass属性
当我们创建类的时候,可以给它添加metaclass属性:
class Foo(object): __metaclass__ = something... [...]
如果我们定义了metaclass属性,Python就会使用这个元类来创建类Foo。
注意,编译器首先读取class Foo(object),这时并不会在内存中创建Foo类。Python会继续查找类定义中的meatclass,如果找到了,就使用它来创建类Foo,如果没有找到,就使用type来创建类。
所以对于以下代码:
class Foo(Bar):
pass
Python工作流程如下:
首先检查Foo中是否具有属性metaclass?
如果找到,就使用metaclass定义的元类在内存中创建一个类对象。
如果在类定义中没有找到这个属性,就在模块级别中进行查找。
如果还是没有找到,就会使用父类Bar中的元类来创建类。
注意:类中的metaclass属性不会被子类继承,但是父类中的class会被继承。
自定义元类
元类的主要作用是在创建类的时候自动改变类。
例如,想要实现模块中所有的类属性都是大写格式。可以定义模块级别的metaclass来实现。
这样模块中所有的类都是通过这个元类来创建的。
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
返回一个类,该类的所有属性名的都为大写
"""
将不是__开头的属性名转为大写字母
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
使用type创建类
return type(future_class_name, future_class_parents, uppercase_attr)
metaclass = upper_attr # 定义模块级别的元类,这样模块中所有类都会使用该元类创建
class Foo():
注意,新式类不支持模块级别的元类,但是可以在类中定义metaclass
bar = 'bip'
print(hasattr(Foo, 'bar'))
输出: False
print(hasattr(Foo, 'BAR'))
输出: True
f = Foo()
print(f.BAR)
Out: 'bip'
也可以将metaclass定义为一个真正的类:
记住type还是一个类,所以可以继承它
class UpperAttrMetaclass(type):
new 会在init之前调用,它会创建并返回一个实例
# 而__init__仅用于初始化,进行一些参数的配置 def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type(future_class_name, future_class_parents, uppercase_attr)
但是上面的做法并不符合OOP的思想,因为它直接调用了type方法,实际上可以调用type的new方法。
class UpperAttrMetaclass(type):
def new(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
调用type.new方法
return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你可能注意到参数upperattr_metaclass, 它代表要实例化的类。当然,我这里取这么个复杂的名字主要是为了明确它的含义。但是,就像self参数一样,所有参数都有其习惯性命名。所以生产环境下的metaclass定义如下:
class UpperAttrMetaclass(type):
def new(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith(''):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.new__(cls, clsname, bases, uppercase_attr)
更好的方式是使用super方法,以便减轻这种继承关系。
class UpperAttrMetaclass(type):
def new(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
元类实际上做了以下三方面的工作:
干涉创建类的过程
修改类
返回修改之后的类
为什么使用类而不是函数来定义元类?
理由如下:
目的更明确,当你阅读UpperAttrMetaclass(type)的时候,你知道它用来做什么。
可以使用面向对象编程,元类可以继承自其它元类,还可以覆盖父类方法。
可以更好的组织代码结构。元类通常用于处理比较复杂的情况。
可以为new、init和call编写钩子,为后续开发者提供便利。
为什么使用元类?
现在,终极问题来了,为什么要使用元类这种模糊且容易出错的功能?
一般情况下,我们并不会使用元类,99%的开发者并不会用到元类,所以一般不用考虑这个问题。
元类主用用于创建API,一个典型的例子就是Django的ORM。
它让我们可以这样定义一个类:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
运行下面的代码:
guy = Person(name='bob', age='35')
print(guy.age)
返回的结果是int类型而不是IntegerField对象。这是因为models.Model使用了元类,它会将Python中定义的字段转换成数据库中的字段。
通过使用元类,Django将复杂的接口转换成简单的接口。
总结
首先,我们知道了类其实就是可以创建实例的对象。而类又是通过元类来创建的。
class Foo(object): pass
id(Foo)
142630324
Python中所有数据类型都是对象,它们要么是类的实例要么是元类的实例。
除了type,它实际上是自身的元类。这一点没法在Python中重现,因为它是在编译阶段实现的。
其次, 元类都是复杂的,对于一般的类是用不着的。可以使用以下两种技巧修改类:
monkey patch
类修饰器
当你需要修改类的时候,99%的情况下可以使用元类。但是99%的情况下,你根本不需要修改一个类。
关键字:Python, metaclass
版权声明
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!