绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
翻译|Stackoverflow上关于Python的高票问答(三)
2020-06-19 09:23:26

Stackoverflow 是一个非常的与程序相关的IT技术问答网站。学习编程专栏打算连载翻译这一系列的问答(在Stackoverflow网站上得到很高赞同的回答和问题),计划进行的方向如下:

Stackoverflow上关于Python的高票问答(Java)(JavaScript)(Php)(C#)每一种语言都会出两到三篇文章,每一篇会有一到三个问题。

感谢@杜杜杜 在翻译过程的帮助以及在后期的校对帮助。

问题:Python中的元类是什么?你用它来干什么?

问题链接:oop - What is a metaclass in Python?

回答:想要了解Python中的元类(metaclass),首先你需要了解Python中的类(class)。Python中的类是十分特别的,他的设计借鉴了Smalltalk语言[1]。

[1]在Smalltalk中所有的东西都是对象,或者应该被当作对象处理:smalltalk

大部分语言中,类(class)就是一段用来描述产生一个对象的代码,Python中的类也是如此

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

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

但是Python中的类可不仅仅是上面所说的这些,Python中的类也是对象。是的,你没听错,对象。可能你会有点迷惑,不过且听分析。

只要你使用了关键词class,Python执行并且产生一个对象(OBJECT)

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

上面的这段代码会在内存中创建一个叫做ObjectCreator的对象(OBJECT)。


这个对象(ObjectCreator是一个class)它自己本身可以产生对象(objects即实例),这就是为什么他是类(class)了。

但是,它(ObjectCreator)仍然是一个对象(OBJECT),以下就是原因:


  • 你可以把它分配给一个变量
  • 你可以复制它
  • 你可以给它添加任何属性
  • 你可以把它作为一个函数的参数

下面是对上面原因进行解释的几个例子:

>>> print(ObjectCreator) # 你可以直接打印输出class,因为它是一个对象(object)
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
... 
>>> echo(ObjectCreator) # 你可以传入一个类(class)作为函数的参数
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # 你可以给类(class)添加属性
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # 可以把类(class)赋给一个变量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Creating classes dynamically

因为类(class)是对象,所以你可以动态的创建他们即在运行时创建他们。

你可以在一个函数(function)中用关键词class创建一个类(class)

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # 返回一个类(class),不是实例。
...     else:
...         class Bar(object):
...             pass
...         return Bar
...     
>>> MyClass = choose_class('foo') 
>>> print(MyClass) # 函数返回一个class,不是实例。
<class '__main__.Foo'>
>>> print(MyClass()) # 这段代码你会创建MyClass类的一个实例化对象
<__main__.Foo object at 0x89c6d4c>

但是,它不是完全动态的,因为你必须把一个类(class)完整的写出来。

既然类(class)是一个对象(OBJECT),那么类肯定是由某样东西产生的。每当你使用class关键词的时候,Python会自动的创建这个对象(Object),但是和Python中大部分的内容一样,Python同样允许你手动去创建它。


还记得type这个函数吗?这个函数可以让你知道一个对象(object)的类型。

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
当然,type还有另一个非常重要的功能:它可以动态的创建类(classes)。把类(class)的描述作为参数传递给type,将返回一个类(class)。我知道同一个函数接收两个不同的参数却有两个完全不同的功能这看起来很愚蠢,这是因为Python的向下兼容性问题【2】。

【2】:如果你不了解向下兼容性可以点击这里:兼容性的向下兼容

type的语法格式如下:

type(name of the class, 
     tuple of the parent class (for inheritance, can be empty), 
     dictionary containing attributes names and values)
type(类名称, 
     父类元组,可以为空(用来继承,可以为空), 
     用来存放类属性和值的字典,可以为空{})

举个例子:

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

上面这个类(class)同样可以这样用type创建:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 创建一个MyShinyClass类的实例
<__main__.MyShinyClass object at 0x8997cec>

type接收一个字典(dict)去定义一个类的属性:

>>> class Foo(object):
...       bar = True

以上代码可以用type描述为:

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

同样的是,上面的这个Foo类可以被当作一个普通的类使用。

>>> 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继承自父类Foo
True

后,当你想要给类(class)添加方法时,只需定义一个函数,并把它纳为类的属性,看下面代码你会理解的更清楚一些。

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

并且你可以在动态的创建了类之后(用type的方式),给类添加更多的方法,就像通常在给类添加方法那样。

>>> def echo_bar_more(self):
...       print('yet another method')
... 
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

总结以上的内容:在Python中,类(class)就是对象(OBJECT),你可以动态的创建类。以上就是当你使用class关键词时,Python所做的事情,同样元类(metaclass)也是如此。


What are metaclasses (finally)

后,元类(metaclass) 到底是什么?

元类(Metaclass) 是创建类(class)的东西。你定义了各种各样的类去描述不同的对象(obj),但是通过上面的内容我们已经了解到在Python中类就是对象。元类(metaclasses)就是创建这些类的东西,你可以认为他们是:描述类(class)的类(class)。可能有点抽象,我们通过下面的代码进一步去理解。

MyClass = MetaClass()
MyObject = MyClass()

就像你上面看到的一样:type将会做这样的事情。

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

这是因为type这个函数实际上就是一个元类, Python用type这个元类去创建每一个类class

也许现在你会想知道,那究竟是为什么type的首字母要小写[2],而不是写成Type。我认为这可能考虑到str、int等关键词的一致性。str是什么?str是创建string的类(class),int是创建integer的类(class)。所以type就是创建class的类(class)。

[2] 你可能不明白为什么会有这种疑惑,请查看:【python】python编码规范

实际上,在Python所有的东西都是对象(object),包括:ints、strings、functions和classes,它们都是对象,并且它们都是从一个class中创建出来的。

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

所以,一个元类(metaclass)就是创建类对象的的东西。 你可以把它称作类工厂(class factory)如果你愿意的话。type是Python中的内建元类,当然你也可以创建自己的元类。

The __metaclass__ attribute

当你写一个类的时候,你可以添加一个 __metaclass__属性,像下面这样:

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

如果你这样操作了,Python将使用你创建的这个元类(metaclass)去创建Foo类。

小心点,这是一个很棘手的问题。首先你书写了Foo这个类,但是Foo类对象在内存中还没有被创建 。

Python将会在类定义中寻找 __metaclass__,如果找到了,Python将会用它去创建类Foo对象,如果没有找到,那么将会使用默认的type去创建这个类(class)

当你像下面这样操作时:

class Foo(Bar):
  pass

实际上Python做了下面的事情:

  1. 在类Foo中有__metaclass__属性吗?
  2. 如果有,用__metaclass__在内存中创建一个叫做Foo的类对象。
  3. 如果Python没有找到__metaclass__,它将会在自己的模块中寻找__metaclass__然后做如2相同的事情。

请注意,这里的 __metaclass__属性不会被继承。这里我们假设我们有一个类Bar,它有__metaclass__属性,并且这个属性指明Bar类是由type函数创建,那么Bar类的所有的子类将都不会继承该动作。

现在,大的问题来了,我们可以在__metaclass__具体放些什么?回答是:一些东西(可以创建一个class的东西) 。那么什么东西可以来创建类呢?type?还是其他的什么东西?

Custom metaclasses

元类(metaclass)存在的意义就是去自动的改变类当它被创建的时候。

试想一个很愚蠢的例子, 你决定在你的代码中所有的类的属性名称必须用大写字母,有几个方法可以达到你的目的,其中一个方法就是模块级设置__metaclass__。如果你使用了这种方法,所有的类将会由这个元类产生,我们只需要告诉元类把所有的属性名称转成大写即可。

幸运的是,__metaclass__可以被随意的调用。

所以下面,我们将会有几个例子:

# 元类所需要的参数和之前你传给type的参数一致
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去创建class类
  return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # 这将影响模块中的所有的classes

class Foo(): # 全局的__metaclass__不会对objects产生影响。
  # 我们可以在这里定义一个__metaclass__,并且这个__metaclass__只会对该类有影响
  bar = 'bip'

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

f = Foo()
print(f.BAR)
# Out: 'bip'
# 记住type类如同int、str类一样,你可以继承它。
class UpperAttrMetaclass(type): 
    # __new__ 这个方法会在 __init__ 方法被调用之前被调用
    # 这个方法创建并且返回一个对象
    # __init__初始化对象并作为参数传递
    # 你很少会用到__new__方法,除非你想要控制对象是怎么被创建的
    # 在这里被创建的对象是class,我们希望可以定制这个class,所以我们可以重写    #    __new__方法
    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[3]。我们直接的调用type并且我们不会重写或者调用父类的__new__

[3]:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。


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。这没什么特别的:__new__方法通常会接收一个类(定义它的类)作为它的个参数。就像你在定义一个普通的方法的时候,self会作为它的个参数去接收实例,在定义class或者类中方法的时候也是如此

当然,为了清晰和通俗易懂我这里使用的名称都很长 ,但是其实像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)
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)

这就是元类(metaclasses),以上差不多就是关于元类所有的东西了。

当你在代码中使用元类的时候,你可能会觉得元类很复杂,但是元类的本身并不复杂。而是因为你在使用元类的时候经常是一些和自省【3】、继承相关的复杂操作。

【3】:关于Python的自省可以查看:Python自省(反射)指南

事实上,用元类( metaclasses)来做一些黑魔法是相当有用的,因此他是个复杂的东西。但是就他们自身来说,还是很简单的:

  • 拦截一个类(class)的创建
  • 修改一个类(class)
  • 返回一个经过修改的类(class)

Why would you use metaclasses classes instead of functions?

既然__metaclass__可以接受各种调用,那我们为什么还要去使用看起来好像更加复杂的类(class)呢。

下面会列出我们这么做的原因:

  • 表意明确,当你使用UpperAttrMetaclass(type),你直接知道他是用来干什么的。
  • 你可以使用面向对象的程序设计 ,元类可以继承元类并重写父类的方法, 元类甚至可以使用元类。
  • 可以使你的代码更加结构化清晰,你永远不会像上面的代码那样零散的使用元类。元类经常用在结构复杂的工程中,写几个函数方法并把它们统一的放在一个类(class)中可以让代码变得清晰易读。
  • 你可能会对__new__, __init__ and __call__很着迷,因为他们允许你做一些不同的事情。尽管通常你所要做的事情都能在__new__里面完成,一些人还是更加愿意使用__init__,该死。
  • 以上就叫做元类(metaclass)

Why would you use metaclasses?

现在大的问题来了,你为什么会去用一些不起眼的并且容易出错的功能。

是的,通常我们不会。

百分之九十九的元类Metaclasses使用者都不用担心它,如果你不知道是否需要他们,答案是不需要(通常那些需要使用元类的人都对元类很熟悉并且知道在什么地方使用它们,并且知道为什么要使用他们。)

Python Guru Tim Peters

通常元类(metaclass)是用来创建API的,比较典型的而一个例子就是Django ORM【4】

【4】:ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射。想要了解更多:【原创】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这个类对象转换成可以执行的数据库(database)语句。

Django通过使用一个简单的API把一些复杂的东西整合进去,而这些借口(API)的背后就是这些元类mtaclass在工作完成指令。

The last word

,你要知道类(class)可以创建实例。

事实上,类(class)他们本身是元类(metalclass)的实例。

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

在Python中所有的东西都是对象(Object),并且他们要不是类(class)的实例,要不就是元类(metaclass)的实例。

Except for type.

除了type

type实际上是他自己本身的元类(metaclass),这可不是你能用纯Python去实现的东西,而是通过在接口层面的一些小改动来实现的。

第二,元类(metalclass)是复杂的,你可能不想去为了改变一个简单的类(class)而去使用它。你可以通过两个不同的技巧去改变类(class):

【5】:关于Python的类装饰器详情请点击了解:Python Decorators入门 (一)

百分之九十九的时候如果你要对一个类做变化,你好使用上面所讲的这些。但是百分之九十九的时候你并不需要对一个类做变化。


学习编程,欢迎关注专栏:学习编程 - 知乎专栏
分享好友

分享这个小栈给你的朋友们,一起进步吧。

人生苦短,不如学Python
创建时间:2020-06-18 16:48:21
Python是一种跨平台的计算机程序设计语言。 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • liuxuhui
    专家
戳我,来吐槽~