python元类Metaclass

2021/9/3 17:35:44

本文主要是介绍python元类Metaclass,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

渣翻StackOverflow高票问答:python中的元类是什么,原问答地址:https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python/100037#100037


高票回答一

类作为对象

在了解元类之前,需要掌握python的类。python从Smalltalk编程语言中借鉴了非常特殊的类的概念。

在大多数编程语言中,类只是描述如何创建对象的代码片段,这在python中也是成立的:

class ObjectCreator(object):
	pass

my_object = ObjectCreator()
print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是类在python中又不仅仅如此,类也是对象。

只要你使用了关键字class,python就会去执行它并且创建一个对象。

class ObjectCreator(object):
	pass

上面这段代码在内存中创建名为ObjectCreator的对象。

这个对象(类)拥有创建对象(实例)的能力,所以它是一个类。

但是它也仍然是一个对象,因此:

  • 你可以把它赋值给一个变量
  • 可以拷贝它
  • 可以给它添加属性
  • 可以把它作为函数参数传递

比如:

# 可以打印一个类因为它是个对象
>>> print(ObjectCreator)
<class '__main__.ObjectCreator'>

>>> def echo(o):
>>> 	print(0)
	
# 作为参数传递
>>> echo(ObjectCreator)
<class '__main__.ObjectCreator'>

# 给类添加属性
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo'
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo

# 赋值给一个变量
>>> ObjectCreatorMirror = ObjectCreator
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态地创建类

既然类是对象,那么可以像任意对象那样动态地创建它。

首先,可以在一个函数中使用class关键字创建类

def choose_class(name):
	if name == 'foo':
		class Foo(object):
			pass
		# 返回类而不是一个实例
		return Foo
        else:
		class Bar(object):
			pass
		return Bar
	
MyClass = choose_class('foo')
# 返回的是一个类而不是一个实例
print(MyClass)
<class '__main__.Foo'>
# 可以从这个类中创建一个对象
print(MyClass())
<__main__.Foo object at 0x89c6d4c>

但是因为还需要自己去写整个类,所以这个不是那么动态。

既然类是对象,那么他们一定可以使用某些东西生成。

当你使用class关键字,python自动创建一个对象,但是和python中大多数的事情一样,它也提供了手动实现的方法。

type是一个可以让你知道对象类型的函数

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

type拥有完全不同的能力,它也可以动态地创建类,type也可以接收类的描述作为参数返回一个类。

(某些函数根据参数的不同而拥有完全不同的用途是有点傻,但这是python向后兼容导致的问题)

type是这样工作的:

type(name, bases, attrs)

在这个段代码中:

  • name: 类命
  • bases: 父类元组(为了继承关系,可以为空)
  • attrs: 包含属性名和值的字典

比如:

class MyShinyClass(object):
	pass

可以用下面的方法手动创建:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

在这里使用MyShinyClass作为类命和保存类引用的变量,它们可以不同,但是没必要复杂化。

type接受字典去定义类的属性,所以

class Foo(object):
	bar = True

可以写作:

Foo = type('Foo', (), {'bar': True})

可以像普通类那样使用

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

当然也可以继承它:

class FooChild(Foo):
	pass

# 同样的:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.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

到这里你可以明白:在python中,类是对象,可以动态地创建类。
这就是当你使用class关键字时python的行为,当使用元类时也是这样做的。

什么是元类

元类是创建类的东西。我们为了创建对象而定义类,但是在python中类也是对象。所以,元类就是创建这些对象的东西。它们是类的类。

MyClass = MetaClass()
my_object = MyClass()

你已经看过了type允许你做的操作:

MyClass = type('MyClass', (), {})

是因为type其实是一个元类,type是python在幕后用来创建所有类的元类。

你可能会疑惑它为什么是小写的,为什么不写作Type?
我猜是为了保持一致性,str是创建strings对象的类,int是创建integer对象的类,type是创建类对象的类。你可以通过__class__属性来查看。

python中的一切都是对象,包括整型,字符串,函数和类。所有的都是对象,并且它们的都是被类创建的。

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

那么__class__的__class__是什么呢?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

所以元类只是创建类对象的东西,如果你想也可以叫它类工厂(class factory),type是python内置的元类,当然也可以创建自己的元类。

__metaclass__属性

在python2中,当你写一个类实现代码的时候可以添加一个__metaclass__属性。

class Foo(object):
	__metaclass__ = something
	[...]

如果你像上面这样做,python使用这个元类来创建类Foo。小心,这种方式很棘手。你先写下class Foo(object),但是类对象Foo还没有在内存中被创建。

python会在类定义中查找__metaclass__,如果找到,他就会用元类来创建对象类Foo,如果没有,使用type来创建类。

class Foo(Bar):
	pass

当执行上面的代码的时候,python执行以下操作:
先查找Foo有没有__metaclass__属性。如果有,使用__metaclass__在内存中创建一个类对象。如果找不到__metaclass__,将在模块级别查找__metaclass__,然后尝试做同样的操作(但仅限于不继承任何东西的类,基本是旧式类)。

如果它找不到任何的__metaclass__,将会使用bar(第一个父类)的元类(可能会是默认的type)来创建类对象。

要小心__metaclass__属性不会被继承,父类的元类(Bar.__class__)会被继承。如果Bar使用了一个用type()方法(而不是type.__new__)来创建Bar__metaclass__属性,子类将不会继承这个行为。

现在问题是可以在__metaclass__里放进什么?答案就是可以创建类的东西。那什么可以创建类呢?type,或者任何它的子类或者使用它的东西。

python3的元类

设置元类的语法在python3中已被改变

class Foo(object, metaclass=something):
	...

比如,__metaclass__属性不再使用,而是作为基类列表的关键字参数。但是元类的行为基本保持不变。

python3中的元类新增的是你也可以使用关键字参数给元类传递属性,比如:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
	...

下面的内容将讲述python是如何处理的

自定义元类

元类的主要目的是在创建类的时候自动地去改变类。

通常是为了创建匹配当前上下文的类的API这样做。

想象你决定在你的模块中的所有的类的属性都要大写。有几种方法可以实现,但是其中一种是设置一个模块级别的__metaclass__。用这种方法,这个模块的所有类都使用这个元类创建,我们只需要告诉这个元类把所有的属性转成大写即可。

幸运得,__metaclass__可以是任何可调用的对象,它不需要是一个普通类。
所以,我们可以用一个函数来开始一个简单的例子

# 这个元类将自动地被传递你过去传递给type的同样的参数
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
	"""
	返回一个属性被转换为大写的类对象
	"""
	uppercase_attrs = {
		attr if attr.startswith("__") else attr.upper(): v
		for attr, v in future_class_attrs.items()
	}
	# type来做创建类的操作
	return type(future_class_name, future_class_parents, uppercase_attrs)

# 这个将会影响模块的所有类
__metaclass__ = upper_attr

# 全局的__metaclass__不会对对象起作用
class Foo():
	# 但是我们可以在这里定义__metaclass__来只影响这个类,并且会影响到对象的子类
	bar = 'bip'
	

可以验证一下:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

现在我们使用一个真正的类作为元类来实现同样的功能

# type其实是像str和int一样的类,所以可以继承自type
class UpperAttrMetaclass(type):
	# __new__方法在__init__方法之前被调用
	# 它是创建并返回对象的方法
	# __init__只初始化作为参数传递的对象
	# 除非想要控制对象是如何创建的,否则很少使用__new__
	# 在这里被创建对象是类,我们想要自定义它
	# 所以重写__new__方法
	# 如果你想也可以在__init__中做一些操作
	# 一些高级用法包括重写__call__我们在这里不做讲述
	def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attrs):
		uppercase_attrs = {
			attr if attr.startswith('__') else attr.upper(): v
			for attr, v in future_class_attrs.items()
		}
		return type(future_class_name, future_class_parents, uppercase_attrs)

现在我们知道了他们的含义,让我们用更短更现实的变量名来重写上面的方法

class UpperAttrMetaclass(type):
	def __new__(cls, clsname, bases, attrs):
		uppercase_attrs = {
			attr if attr.startswith("__") else attr.upper(): v
			for attr, v in attrs.items()
		}
		return type(clsname, bases, uppercase_attrs)

你可能已经注意到了额外的参数cls,它没什么特殊的:__new__始终接收定义它的类作为第一个参数,就像普通方法的self参数,它接受实例作为第一个参数,或者作为类方法的时候接受定义它的类作为第一个参数。

但是这不是合适的面向对象OOP思想。我们可以直接调用type,并且不重写或者调用父类的__new__

class UpperAttrMetaclass(type):
	def __new__(cls, clsname, bases, attrs):
		uppercase_attrs = {
			attr if attr.startswith("__") else attr.upper(): v
			for attr, v in attrs.items()
		}
		
		return type.__new__(cls, clsname, bases, uppercase_attrs)

使用super可以让它更清晰明了,它将会简化继承(因为你当然可以从type中继承,从元类中继承,从而拥有元类)

class UpperAttrMetaclass(type):
	def __new__(cls, clsname, bases, attrs):
		uppercase_attrs = {
			attr if attr.startswith("__") else attr.upper(): v
			for attr, v in attrs.items()
		}
		
		return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attrs)

在python3中,如果你像下面这样使用关键字参数调用

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
	...

它在元类中会转化成这样去使用:

class MyMetaclass(type):
	def __new__(cls, clsname, bases, dct, kwargs1=default):
		...

之所以使用元类的代码这么复杂不是因为元类,而是因为通常你使用元类去操作依赖于内省,操纵继承,变量如__dict__等等的扭曲的事情上

确实元类在做这些黑魔法操作上特别有用,所以才会有这么复杂的东西,但是他们本身是很简单的:

  • 拦截类的创建
  • 修改类
  • 返回修改后的类

为什么要使用元类而不是函数?

既然__metaclass__接受任何可调用对象,既然使用类明显得更复杂为什么还要使用类?

下面是这么做的几个原因:

  • 拦截更清晰,当你去看UpperAttrMetaclass(type)的时候,你知道接下来会发生什么
  • 可以使用面像对象思想OOP,元类可以从元类继承,重写父类方法,元类甚至可以使用元类
  • 如果你指定了一个一个元类类,不是元类函数,类的子类将会是元类的实例
  • 可以使你的代码更加的结构化,你不会像上面的例子一样使用元类做一些琐碎的小事。它通常用于更复杂的功能。拥有创建几个方法并且组织他们在一个类中的能力对代码的易读性是非常有用的。
  • 你可以执着于__new__,__init____call__这些允许你做不同操作的函数,即使通常你可以全部在__new__中实现,也有一些人更乐意使用__init__.
  • 他们被称为元类一定意味着什么。

你为什么使用元类

问题是为什么要用一些隐晦的容易出错的特性呢?
其实通常我们不用。

元类是更深奥的魔法,99%的用户都不需要担心它。如果你不确定你是否需要它们,那么你就不需要(需要它们的人一定很确定他们需要元类并且不需要解释)
_Python Guru Tim Peters

使用元类的主要应用场景是创建API。一个典型的例子就是Django的ORM,它允许你像下面这样去定义:

class Person(model.Model):
	name = models.CharField(max_length=30)
	age = models.IntegerField()

但是如果你这样做:

person = Person(name='bob', age='35')
print(person.age)

它不会返回一个IntegerField对象,而是返回一个int,甚至可以直接从数据库中获取。
这可能是因为models.Model定义了__metaclass__,它使用了一些魔法将你使用简单语句定义的Person转化为连接到数据库字段的复杂钩子。

Django通过暴露一个简单的API和使用元类,从这个API中重建代码来完成幕后的实际操作,使复杂的东西看起来简单。

结语

首先,你知道类是可以创建实例的对象。
实际上类就是他们本身的实例,元类的实例。

>>> class Foo(object): pass
>>> id(Foo)
142630324

python中的一切都是对象,它们都要么是类的实例,要么是元类的实例。除了type

type其实是它自己的元类。它不是你可以在纯python中重现的东西,它是在实现级别上做了一些欺骗才实现的。

其次,元类很复杂,你可能不像用它们来做很简单的类修改,你可以通过下面两个不同的技术来改变类:

  • 猴子补丁
  • 类装饰器

99%的情况,你最好使用这些来改变类。
但是98%的情况你完全不需要修改类。


高票回答二

元类是类的类,一个类定义了类的实例的行为,同样一个元类定义了类的行为。类是元类的实例。

在python中可以为元类使用任意的可调用对象,但是更好的实现是让它成为一个真正的类。type是python的常见元类,type本身是一个类,并且是它自己的类型。虽然在python中不能完全重现类似type的东西,但是python里还是有些小技巧,为了创建自己的元类,只需要继承type

一个元类最常用作类工厂(class-factory),当你通过调用类来创建对象的时候,python通过调用元类来创建一个新类(当执行class语句的时候)。通过__init____new__方法的结合,元类允许你在创建一个一个类的时候做一些额外的操作,比如用一些注册信息来注册类,或者使用其他东西完全取代类。

当执行class语句(class statement)的时候,python首先像执行普通的代码块那样去执行class语句体。结果命名空间(一个字典)暂存了这个即将生成的类(class-to-be)的属性。元类是通过查找这个即将生成的类(元类被他继承)的__metaclass__属性或者__metaclass__全局变量来决定的。然后使用此类类命,基类,属性调用元类来实例化它。

然而,元类实际定义了类的类型,而不仅仅是类工厂,所以可以使用元类来做的事情很多,比如,可以在元类上定义常规方法,这些元类方法就类似于类方法,可以在不实例化类的时候被调用,但是又不像类方法,他们不能被类的实例对象调用. type.__subclasses__()就是一个在type元类里这样的方法的例子。同样也可以定义常规魔术方法,比如__add__,__iter__,__getattr__来实现或者改变类行为。

下面是一些零碎的例子:

def make_hook(f):
	"""将foo方法变为__foo__的装饰器"""
	f.is_hook = 1
	return f

class MyType(type):
	def __new__(mcls, name, bases, attrs):
		if name.startswith('None'):
			return None;
		
		# 遍历属性确认他们是否需要重命名
		newattrs = {}
		for attrname, attrvalue in attrs.iteritems():
			if getattr(attrvalue, 'is_hook', 0):
				newattrs['__%s__' % attrname] = attrvalue
			else:
				newattrs[attrname] = attrvalue
				
		return super(MyType, mcls).__new__(mcls, name, bases,newattrs)
	
	def __init__(self, name, bases, attrs):
		super(MyType, self).__init__(name, bases, attrs)
		
		# classregistry.register(self, self.interfaces)
		print "Would register class %s now." % self
		
	def __add__(self, other):
		class AutoClass(self, other):
			pass
		return AutoClass
		# 或者自动生成类命和类
		# return type(self.__name__ + other.__name__, (self, other), {})
		
	def unregister(self):
		# classregistry.unregister(self)
		print "Would unregister class %s now." % self
		
class MyObject:
	__metaclass__ = MyType
	
class NoneSample(MyObject):
	pass

# will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
	def __init__(self, value):
		self.value = value
		
	@make_hook
	def add(self, other):
		return self.__class__(self.value + other.value)
	
# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__


这篇关于python元类Metaclass的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程