Python很简单?学会魔术方法才算入门!

 点击上方“中兴开发者社区”,关注我们

每天读一篇一线开发者原创好文640?wx_fmt=png&wxfrom=5&wx_lazy=1

Python中的元类是什么?

原问题地址:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

问题

什么是元类?使用它们能做什么?

答案 1

元类是类的一种。正如类定义了实例功能,元类也定义了类的功能。类是元类的实例。

在Python中你可以随意调用元类(参考Jerub的回答),实际上更有用的方法是使其本身成为一个真正的类。type是Python中常用的元类。你可能觉得奇怪,是的,type本身就是一个类,而且它就是type类型。你无法在Python中重新创造出完全像type这样的东西,但Python提供了一个小把戏。你只需要把type作为子类,就可以在Python中创建自己的元类。

元类是最常用的一类工厂。就像你通过调用类来创建类实例,Python也是通过调用元类来创建一个新类(当它执行class语句的时候),结合常规的 __init____new__方法,元类可以允许你在创建类的时候做一些“额外的事情”,like registering the new class with some registry(暂时不知道这句话的含义,不知道怎么翻译,字面意思是:就像用某个注册表来注册新的类那样),甚至可以用别的东西完全代替已有的类。

当Python执行class语句时,它首先把整个的class语句作为一个正常的代码块来执行。由此产生的命名空间(一个字典)具有待定类的属性。元类取决于待定类的基类(元类是具有继承性的)、或待定类的__metaclass__属性(如果有的话)或__metaclass__全局变量。接下来,用类的名称、基类和属性调用元类,从而把元类实例化。

然而,元类实际上定义的一个类的类型,而不只是类工厂,所以你可以用元类来做更多的事情。例如,你可以定义元类的一般方法。这些元类方法和类方法有相似之处,因为它们可以被没有实例化的类调用。但这些元类方法和类方法也有不同之处,元类方法不能在类的实例中被调用。type.__subclasses__()是关于type的一个方法。你也可以定义常规的“魔法”函数,如__add__, __iter____getattr__,以便实现或修改类的功能。

摘抄一个例子:

def make_hook(f):
 """Decorator to turn 'foo' method into '__foo__'"""
 f.is_hook = 1
 return f

class MyType(type):
 def __new__(cls, name, bases, attrs):

if name.startswith('None'):
 return None

# Go over attributes and see if they should be renamed.
 newattrs = {}
 for attrname, attrvalue in attrs.iteritems():
 if getattr(attrvalue, 'is_hook', 0):
 newattrs['__%s__' % attrname] = attrvalue
 else:
 newattrs[attrname] = attrvalue

return super(MyType, cls).__new__(cls, 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
 # Alternatively, to autogenerate the classname as well as the class:
 # 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__
shareedit

答案 2

作为对象的类

在理解元类之前,你需要掌握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
...

这个对象(类)本身就能够创建一些对象(实例),这就是为什么它是类。

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

  • 你可以将它分配给一个变量

  • 你可以复制它

  • 你可以增加它的属性

  • 你可以把它作为一个功能参数来用

例如:

>>> 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())
<__main__.ObjectCreator object at 0x8997b4c>
Creating classes dynamically

类的动态创建

既然类是对象,你就能动态地创建它们,就像创建任何对象那样。

首先,你可以在一个使用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
<__main__.Foo object at 0x89c6d4c>

但它并不是动态的,因为你还是要自己写出整个类。

类是对象,它们必须由某种东西产生。

当你使用关键字class时,Python会自动创建该对象。但是,与Python中的大多数东西一样,它给你提供了一个手工操作的方法。

还记得type函数吗?这个一个好用的旧函数,它能让你了解一个对象的类型:

>>> print(type(1))

>>> print(type("1"))

>>> print(type(ObjectCreator))

>>> print(type(ObjectCreator()))

type有着完全不同的能力,它还可以动态地创建类。type可以把对于类的描述作为参数,并返回一个类。

(同样的函数根据你传入的参数而有完全不同的用途。我知道这看起来有点怪。这是因为Python向后兼容。)

type是这样应用的:

例如:

>>> class MyShinyClass(object):
... pass

可以这样子来进行手动创建

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(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)

>>> 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)

>>> 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

你现在明白了:在Python中,类就是对象,你可以动态地创建类。

这就是关键字class在Python中的应用,它是通过使用元类来发挥作用。

元类是什么

元类是用来创建类的东西。

你通过定义类来创建对象,对吧?

但我们知道Python的类就是对象。

元类用于创建这些对象,元类是类的类,你可以这样来描述它们:

MyClass = MetaClass()
MyObject = MyClass()

你已经看到了type可以这样来用:

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

这是因为type实际上是一个元类,作为元类的type在Python中被用于在后台创建所有的类。

现在你感到疑惑的是为什么这里是小写的type,而不是大写的Type?

我想这是为了与创建字符串对象的类str和创建整数对象的类int在写法上保持一致。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...
 [...]

如果你这样做,Python会使用元类来创建Foo这个类。

小心,这是棘手的。

这是你是首次创建class Foo(object),但是类对象Foo在内存中还没有被创建。

Python会在类定义中寻找__metaclass__。如果找到它,Python会用它来创建对象类Foo。如果没有找到它,Python将使用type来创建这个类。

把上面的话读几遍。

当你写下:

class Foo(Bar):
 pass

Python会实现以下功能:

Foo有没有__metaclass__的属性?

如果有,通过借鉴__metaclass__,用Foo这个名字在内存中创建一个类对象(我说的是一个类对象,记住我的话)。

如果Python找不到__metaclass__,它会在模块层级寻找__metaclass__,并尝试做同样的事情(但这只适用于不继承任何东西的类,基本上是旧式类)。

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

小心点,__metaclass__属性不会被继承,而父类的元类(Bar.__class__)将会被继承。如果Bar所用的__metaclass__属性是用type()来创建Bar(而不是type.__new__()),它的子类不会继承这种功能。

现在最大的问题是,你可以在__metaclass__中写些什么?

答案是:可以创建类的东西。

什么可以创建类?type,或者父类。

自定义元类

一个元类的主要目的是当它被创建时,这个类可以自动改变。

通常在API中,可以创建一个元类,以之匹配于当前的内容。

想象一个愚蠢的例子:模块中的所有类的属性都应该用大写字母来写。你有几种方法,其中的一种方法就是在模块层次上设置__metaclass__

这样,这个模块中所有的类都将使用这个元类来创建,我们只需要告诉元类把所有属性改为大写。

幸运的是,__metaclass__实际上可以任意调用,它并不需要成为一个正式的类(我知道,名字中带有“class”字样的东西未必就是类,想想看吧…但这是有益的)。

因此,我们将通过使用函数来举一个简单的例子。

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
 """
 Return a class object, with the list of its attribute turned into uppercase.
 """

# pick up any attribute that doesn't start with '__' and uppercase it
 uppercase_attr = {}
 for name, val in future_class_attr.items():
 if not name.startswith('__'):
 uppercase_attr[name.upper()] = val
 else:
 uppercase_attr[name] = val

# let `type` do the class creation
 return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
 # but we can define __metaclass__ here instead to affect only this class
 # and this will work with "object" children
 bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

现在,让我们完全照做,但使用一个真的类作为元类:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type): 
 # __new__ is the method called before __init__
 # it's the method that creates the object and returns it
 # while __init__ just initializes the object passed as parameter
 # you rarely use __new__, except when you want to control how the object
 # is created.
 # here the created object is the class, and we want to customize it
 # so we override __new__
 # you can do some stuff in __init__ too if you wish
 # some advanced use involves overriding __call__ as well, but we won't
 # see this
 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)

但这不是真正的面向对象编程。我们直接调用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

# reuse the type.__new__ method
 # this is basic OOP, nothing magic in there
 return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

你可能已经注意到额外的参数upperattr_metaclass,它没有什么特别之处:__new__upperattr_metaclass为第一参数,并且在upperattr_metaclass中被定义。就像你把self作为实例的第一个参数那样,或者作为类方法的第一个参数。

当然,我在这里为了清晰起见,使用了一个很长的名称,但就像self一样,所有的参数都有习惯的名称。所以,在开发实践中所写的元类看起来是这样的:

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使它更清楚,这样将缓解继承(因为,是的,你可以拥有元类,继承metaclasses,继承type):

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)

就是这样。关于元类真的是没有更多要讲的了。

使用元类的代码复杂的原因并不在于元类,那是因为你通常用元类来实现一些奇怪的功能,而这些功能要依靠自省、继承、变量,如:__dict__等。

的确,元类对于“魔法”特别有用,这些事务是复杂的,但元类本身很简单:

  • 拦截类的创建

  • 修改类

  • 返回修改后的类

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

__metaclass__可以被任意调用。既然类明显更复杂,你为什么还要使用它呢?

这样做有几个理由:

  • 目的明确。当你读UpperAttrMetaclass(type)时,你知道要遵循的是什么。

  • 可以使用面向对象编程。元类可以继承元类、重写父类的方法。元类甚至可以使用元类。

  • 可以优化代码。你从不为像上面例子中琐碎的东西而使用元类。它通常用于复杂的东西。在一个类中有多种方法并且将它们优化组合,是非常有价值的,使得代码可读性更强。

  • 你可能喜欢使用__new____init____call__。它们能帮你实现不同的功能。尽管你通常可以用__new__来实现所有的功能,有些人还是更喜欢使用__init__

  • 这些被称为元类。可恶!它肯定意味着什么!
    你为什么会使用元类?

现在的大问题是:为什么你会使用一些复杂难懂、容易出错的特性?

嗯,通常你不会这样做:

元类是更深层次的魔法,超过99%的用户不需要担心。不要怀疑你是否需要元类(那些真正需要元类的人对此确定无疑,并且不需要解释为什么)。
——Python专家蒂姆﹒彼得斯

元类的主要使用案例是创建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)

它不会返回到一个IntegerField对象。它会返回到一个int,甚至可以直接从数据库读取。

这是可以实现的,因为models.Model定义了__metaclass__。它使用魔法把你刚才用简单语句定义的Person转化成一个复杂的钩子连接到数据库字段。

通过显示一个简单的API和元类,Django使复杂的东西看起来简单,再从API重构代码去完成真正的幕后工作。

最后的话

首先,你知道,类是可以创建实例的对象。

事实上,类本身就是实例。在元类中

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

在Python中,一切都是对象,它们都是类的实例或元类的实例。

但是type除外。

type实际上是自己的元类。你无法在纯粹的Python中复制它,所以只能在实施层面做点小把戏。

其次,元类是复杂的。你可能不想把它们用于非常简单的类。你可以用两种不同的技术来改变类:

  • 猴子补丁monkey patching

  • 类装饰器

当你需要改变类的时候,99%的情况下,使用它们是明智之举。

但99%的时间,你根本不需要改变类。

转自:https://github.com/qiwsir/StackOverFlowCn/blob/master/302.md

Python很简单?学会魔术方法才算入门!_第1张图片

你可能感兴趣的:(Python很简单?学会魔术方法才算入门!)