编程语言
首页 > 编程语言> > python元类Metaclass

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)

在这个段代码中:

比如:

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__接受任何可调用对象,既然使用类明显得更复杂为什么还要使用类?

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

你为什么使用元类

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

元类是更深奥的魔法,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,type,元类,print,Metaclass,class
来源: https://www.cnblogs.com/masami-/p/15219919.html