博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python很简单?学会魔术方法才算入门!
阅读量:4046 次
发布时间:2019-05-25

本文共 12456 字,大约阅读时间需要 41 分钟。

永久地址:http://www.ssforce.cn/?p=289

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 fclass 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] = attrvaluereturn 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." % selfdef __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." % selfclass MyObject: __metaclass__ = MyTypeclass 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 classExample.unregister()inst = Example(10)# Will fail with an AttributeError#inst.unregister()print inst + instclass Sibling(MyObject): passExampleSibling = 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 ExampleSiblingprint 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 FooTrue

最终,你会想把一些方法添加到你的类中。需要用一个适当的识别标志来定义一个函数,并将其指定为一个属性。

>>> 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 moduleclass 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: Falseprint(hasattr(Foo, 'BAR'))# Out: Truef = Foo()print(f.BAR)# Out: 'bip'

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

# remember that `type` is actually a class like `str` and `int`# so you can inherit from itclass 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] = valreturn 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] = valreturn 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] = valreturn 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

分享朋友圈 也是另一种赞赏

The more we share, The more we have

 

欢迎加入数据君高效数据分析社区


加我私人微信进入大数据干货群:tongyuannow 





目前100000+人已关注加入我们

       

       



转载地址:http://iezci.baihongyu.com/

你可能感兴趣的文章
dba 常用查询
查看>>
Oracle 异机恢复
查看>>
Oracle 12C DG 搭建(RAC-RAC/RAC-单机)
查看>>
Truncate 表之恢复
查看>>
Oracle DG failover 后恢复
查看>>
mysql 主从同步配置
查看>>
为什么很多程序员都选择跳槽?
查看>>
mongdb介绍
查看>>
mongdb在java中的应用
查看>>
区块链技术让Yotta企业云盘为行政事业服务助力
查看>>
Yotta企业云盘更好的为媒体广告业服务
查看>>
Yotta企业云盘助力旅游行业新发展
查看>>
Yotta企业云盘助力科技行业创高峰
查看>>
Yotta企业云盘更好地为教育行业服务
查看>>
Yotta企业云盘怎么帮助到能源化工行业
查看>>
企业云盘如何助力商业新发展
查看>>
医疗行业运用企业云盘可以带来什么样的提升
查看>>
教育数字智能化能为现有体系带来新的起点
查看>>
媒体广告业如何将内容资产进行高效地综合管理与利用
查看>>
能源化工要怎么管控核心数据
查看>>