python-面向对象-07-类对象/元类metaclass

python-面向对象-07类对象/元类metaclass

1.引题

类对象?是什么鬼?对象不是类的实例么,那类对象是什么意思?在Python中,普通类既是一个类,但同时也是一个对象(也叫做类对象),对的,你没有看错。不信?我们看一个例子。

class Student:
    pass        # 空语句

# 实例化对象
s1 = Student()

print("对象s1的id和type和__class__")
print(id(s1))
print(type(s1))
print(s1.__class__)

print("--" * 10)
print("类Student的id和type和__class__")
print(id(Student))
print(type(Student))
print(Student.__class__)

执行结果:

对象s1的id和type
4489106320
<class '__main__.Student'>
<class '__main__.Student'>
--------------------
类Student的id和type
140254813901664
<class 'type'>
<class 'type'>

通过执行结果可以看到:

  1. 首先对象s1和类Student的id值,也就是存储的内存地址,数值相差比较大,至少说明对象和类是分区域存储的。
  2. 在python3中type函数和__class__的效果相同,也就是说在python3中这两者是一致(后面有补充说明)。
  3. __class__属性会指向该实例对应的类,类Student的__class__属性值为<class 'type'>,由此我们可以知道类Student是类type的实例。从这个角度上来说,普通类也是一个对象。

2.Metaclasses登场

Metaclasses中文名称是元类,元类是一个深奥的OOP概念,隐藏在几乎所有Python代码背后。不管你是否意识到,你都在使用它们。在大多数情况下,你不需要意识到它。大多数Python程序员很少(如果有的话)需要考虑元类。

在我自己的实际工作中几乎没有特意使用过元类。

python之禅的作者Tim Peters对于Metaclasses的描述:

“Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).”

译文:

元类的魔力比99%的用户所担心的都要深。如果你想知道你是否需要它们,你不需要(真正需要它们的人肯定知道他们需要它们,不需要解释为什么)。

— Tim Peters

我第一次看到这句话的时候,也有一些蒙圈。下面我们就一步一步来揭开Metaclasses的神秘面纱。

3.旧式类和新式类

在Python领域中,类可以是两种变体之一。官方的术语对于这一部分还没有确定,所以它们被非正式地称为旧式和新式类。

3.1 旧式(Old-Style)类

对于旧式的类,类和类型不是完全相同的东西。旧式类的实例总是由一个称为instance的内置类型实现。如果obj是一个旧式类的实例,那么obj.__class__指向具体的类,但类型(obj)总是instance。下面的例子摘自Python 2.7命令行:

>>> import sys
>>> print sys.version
2.7.16 (default, Dec 13 2019, 18:00:32)
[GCC 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.32.4) (-macos10.15-objc-s
>>> class Foo:
...     pass
...
>>> x = Foo()
>>> x.__class__
<class __main__.Foo at 0x10b383a10>
>>> type(x)
<type 'instance'>

3.2 新式(New-Style)类

在python3中,新型的类统一了类和类型的概念。如果obj是一个新样式类的实例,那么type(obj)与obj._ class__是相同的,均为class,不在继续使用instace.

>>> import sys
>>> sys.version
'3.7.7 (v3.7.7:d7c567b08f, Mar 10 2020, 02:56:16) \n[Clang 6.0 (clang-600.0.57)]'
>>> class Foo:
...     pass
...
>>> obj = Foo()
>>> obj.__class__
<class '__main__.Foo'>
>>> type(obj)
<class '__main__.Foo'>
>>> obj.__class__ is type(obj)
True

3.3 新式类和旧式类总结

在python2中,类默认是旧式类。在python2.2版本之前,新式类根本不支持。在python2.2以及后续的python2版本中,能够创建新式类,但是必须指定为新式类。
在python3中,所有的类默认是新式类。因此,在python3中,type(类)类.__class__是一致的。

4 类和类型

4.1 代码分析

还是用我们刚才使用的例子进行分析

>>> class Foo():
...     pass
...
>>> x = Foo()
>>> type(x)
<class '__main__.Foo'>
>>> type(x)
<class '__main__.Foo'>

在这个例子中:

  • 对象x是类Foo的实例。
  • 类Foo是type元类(metaclass)的实例。
  • type元类也是type元类(metaclass)的实例,所以type元类是自身的一个实例。
示意图

4.2 使用type函数动态定义类

type函数官方定义:

    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass
  • <bases>指定基类的元组,该类从该元组继承基类.这成为类的__bases__属性。
  • <dict>指定包含类主体定义的名称空间字典,这成为类的__dict__属性。

以这种方式调用type()将创建类型元类的一个新实例。换句话说,它动态地创建一个新类。

示例:

>>> Foo = type('Foo', (), {})
>>> x = Foo()
>>> x
<__main__.Foo object at 0x106869f90>

再一次说明,普通类是通过type类创建出来的。

5.自定义Metaclasses

5.1 代码分析

>>> class Foo:
...     pass
...
>>> f = Foo()

表达式Foo()创建类Foo的一个新的实例。当解释器执行Foo()的时候,会执行如下操作:

  1. 按照类继承关系,首先类Foo的父类__call__()方法被调用(后续的笔记会补充这一部分的说明)。因为Foo是一个标准的新式类,那么Foo父类就是type元类(metaclasss),所以type的__call__()的方法被调用。
  2. __call__()方法按照顺序调用如下方法:
    • __new__()
    • __init__()

如果类Foo没有定义__new__()__init()__方法,默认方法则从类Foo的父类继承而来。但是如果类Foo定义了以上两个方法,则重写了父类的对应的方法,当我们实例化Foo的时候,可以Foo的实例进行自定义。

>>> class Meta(type):
...     def __new__(cls, name, bases, dct):
...             x = super().__new__(cls, name, bases, dct)
...             x.attr = 100
...             return x
...
>>> class Foo(metaclass=Meta):
...     pass
...
>>> Foo.attr
100
>>> class  Bar(metaclass=Meta):
...     pass
...
>>> Bar.attr
100

首先定义了 一个继承type元类的自定义类Meta,因为继承type元类,所以类Meta是一个自定义元类。然后重写其中的__new__方法,对每一个通过自定义元类Meta创建的普通类,都增加一个类属性attr,并且给类属性赋值100。后面通过两次指定(metaclass=Meta)的方式来创建类,来实现调用自定义元类创建类。最后在两个类中均存在属性attr,并且对应的值为100.

6.自定义metaclass是必需的么?

刚才我们通过自定义元类的方式实现功能,实际上能够通过其他方式也能够实现,下面我们展示其中的两种方法。
第一种方法:使用普通继承

class Base:
    attr = 100

class X(Base):
    pass

class Y(Base):
    pass

class Z(Base):
    pass

print(X.attr)
print(Y.attr)
print(Z.attr)

执行结果:

100
100
100

第二种方法:使用装饰器

def decorator(cls):
    """定义一个带类属性attr=100的新类"""
    class NewClass(cls):
        attr = 100
    """返回类"""
    return NewClass

@decorator
class X:
    pass

@decorator
class Y:
    pass

@decorator
class Z:
    pass

print(X.attr)
print(Y.attr)
print(Z.attr)

执行结果:

100
100
100

正如python之禅的作者Tim Peters所建议的那样:如果对于元类不清楚,就不要考虑使用元类。通常没有必要创建自定义元类。如果手头上的问题可以用一种更简单的方法来解决,那么它可能应该是这样的。尽管如此,理解元类仍然是有益的。

参考列表:

  1. https://realpython.com/python-metaclasses/ realpython网站有很多很不错的python教程

备注:
更多精彩博客,请访问:聂发俊的技术博客
对应视频教程,请访问:python400
完整markdown笔记,请访问: python400_learn_github

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352