文档章节

Python 类与元类的深度挖掘 II

rainyear
 rainyear
发布于 2016/05/04 23:18
字数 1106
阅读 396
收藏 9

上一篇解决了通过调用类对象生成实例对象过程中可能遇到的命名空间相关的一些问题,这次我们向上回溯一层,看看类对象本身是如何产生的。

我们知道 type() 方法可以查看一个对象的类型,或者说判断这个对象是由那个类产生的:

print(type(12))
print(type('python'))
<class 'int'>
<class 'str'>
class A:
    pass
print(type(A))
<class 'type'>

通过这段代码可以看出,类对象 A 是由type() 产生的,也就是说 type 也可以用来产生新的对象,而且产生的是类对象,因此它是所有类对象的类:

print(type.__doc__)
type(object_or_name, bases, dict)
type(object) -> the object's type
type(name, bases, dict) -> a new type

class 定义类的语法实际上转化为 type(name, bases, dict),其中 name 参数为类的名字,bases 为继承父类的元组,dict 为类的属性和方法:

class A:
    pass
# 实际上等于
B = type('A', (), {})

print(A.__name__ == B.__name__)
True

理论上说这就是元类的意义,但从实际的角度出发显然使用 class 语法更方便、合理,而元类的实际意义则是通过继承 type 类来构造一个新的元类,并进行特定的操作以产生具有特定行为的类对象。这样看来它的本质与普通的类对象没有差异,只不过继承的是 type 类。

在生成实例时是通过调用 __init__ 方法进行初始化的,而实际上在此之前会先调用 __new__ 方法用于创建实例,再通过 __init__ 初始化,就好像 __new__ 负责声明变量,而 __init__ 负责对声明的变量进行初始化一样。这里有一个规则是 __new__(cls,) 的返回值必须是 cls 参数的实例,否则 __init__ 将不会触发,例如在 enum.Enum 的定义中,由于枚举类型是单例模式,因此在定义 __new__ 的时候没有返回其实例,也就不会进行初始化:

class Enum:
    def __new__(cls, value):
        print(cls, value)
        return value
    def __init__(self):
        print("Will not be called!")
e = Enum(1)
<class '__main__.Enum'> 1

通常情况下自己定义 __new__ 需要通过调用父类的 __new__ 方法创建一个 cls 的实例,同样在定义元类的时候则是调用上面提到的 type 的用法(因为元类继承自 type):

class MetaEnum(type):
    def __new__(metaclass, name, base, attrs):
        print("Metaclass: {}\nName: {}\nParents: {}\nAttributes: {}".format(metaclass, name, base, attrs))
        return super().__new__(metaclass, name, base, attrs)
class Enum(metaclass=MetaEnum):
    # Python 2.7 中定义元类的方法是使用 __metaclass__ 变量
    # [PEP 3115](https://www.python.org/dev/peps/pep-3115/)
    # 将 Python 3.0 以后语法改为 class Cls(metaclass=Meta)
    test = 0
Metaclass: <class '__main__.MetaEnum'>
Name: Enum
Parents: ()
Attributes: {'__qualname__': 'Enum', '__module__': '__main__', 'test': 0}

此时我们再来看 Enum 的类,已经不再是 type 而是其元类 MetaEnum

type(Enum)
__main__.MetaEnum

除了 __new__ 方法之外,PEP 3115 还定义了 __prepare__ 属性,用于设定初始化的命名空间(即 type 的第 3 个参数),还是以 enum.Enum 为例,我们需要限制枚举类型中属性名称不得重复使用,则可以通过元类限制类的行为:

# 定义新的字典类,在赋值新的 dict[k] = v 时
# 检查 k 是否重复
class _EnumDict(dict):
    def __init__(self):
        super().__init__()
        self.members = []
    def __setitem__(self, k, v):
        if k in self.members:
            raise TypeError("Attempted to reuse key: '{}'".format(k))
        else:
            self.members.append(k)
            super().__setitem__(k, v)
            
class MetaEnum(type):
    @classmethod
    def __prepare__(metaclass, cls, bases):
        return _EnumDict()
    def __new__(metaclass, name, base, attrs):
        return super().__new__(metaclass, name, base, attrs)
class Enum(metaclass=MetaEnum):
    pass

class Color(Enum):
    try:
        red = 1
        red = 2
    except TypeError:# 这里没有使用 as err: 的原因是?
        print("TypeError catched")
TypeError catched

Python 中一切皆为对象,所有的对象都是某一类的实例,或是某一元类的实例,type 是自己的元类也是自己的实例:

metaclass

总结

元类在 Python 中属于比较深层的黑魔法,在一般的日常应用中可能并不常用,但理解其背后的原理对于理解 Python 面向对象编程以及一切皆为对象的理念很有帮助;如果你需要对类进行深度改造,至少要知道从何入手。

参考

  1. What is a metaclass in Python

pyhub

© 著作权归作者所有

rainyear
粉丝 2
博文 9
码字总数 10955
作品 0
杭州
CTO(技术副总裁)
私信 提问
加载中

评论(1)

pennymei
pennymei
OneAPM 是唯一的一个可以帮助你查看 Python 应用程序方方面面的工具。无论是从终端的用户体验还是服务器监控,OneAPM 支持追踪各种问题,例如:数据库查询缓慢、第三方 API 接口和 Web 服务器甚至数据缓存层缓慢等。可以在生产环境下监控你的应用。快来官网注册体验吧~
“Python机器(深度)学习算法原理与案例实现”培训班通知

“Python机器(深度)学习算法原理与案例实现”培训班通知 各科研院所及企业单位: 随着人工智能与数据科学的发展,在商业、经济及其他领域中基于数据和分析去发现问题并作出科学客观的决策越...

会务组周恒
2018/08/25
17
0
“Python机器(深度)学习算法原理与案例实现”培训班通知

“Python机器(深度)学习算法原理与案例实现”培训班通知 各科研院所及企业单位: 随着人工智能与数据科学的发展,在商业、经济及其他领域中基于数据和分析去发现问题并作出科学客观的决策越...

会务组周恒
2018/08/25
264
0
Python爬虫与文本挖掘核心技术、会议研修通知

Python作为一门脚本语言,它灵活、易用、易学、适用场景多,实现程序快捷便利,早已经成为程序员们的一门编程利器。Python这门编程语言包罗万象,可以说掌握了python,除了一些特殊环境和高度...

python深度学习
07/29
4
0
Python基础总结成千行代码,让Python入门更简单!

只要学会这千行代码,不管你是零基础还是弱基础或是没有接触过编程,都可以快速入门Python!当时我就不信邪啊,等我看完之后,即使作为一个Python老鸟了,还是领会到了很多教授他的独特见解!...

糖宝lsh
2018/09/10
254
0
深刻理解Python中的元类(metaclass)

深刻理解Python中的元类(metaclass) 译注:这是一篇在Stack overflow上很热的帖子。提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解。他知道这肯定...

memorybox
2013/05/05
322
0

没有更多内容

加载失败,请刷新页面

加载更多

Qt程序打包发布方法(使用官方提供的windeployqt工具)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/toTheUnknown/article/details/81748179 如果使用到了Qt ...

shzwork
33分钟前
5
0
MainThreadSupport

MainThreadSupport EventBus 3.0 中的代码片段. org.greenrobot.eventbus.MainThreadSupport 定义一个接口,并给出默认实现类. 调用者可以在EventBus的构建者中替换该实现. public interface ...

马湖村第九后羿
54分钟前
3
0
指定要使用的形状来代替文字的显示

控制手机键盘弹出的功能只能在ios上实现,安卓是实现不了的,所以安卓只能使用type类型来控制键盘类型,例如你要弹出数字键盘就使用type="number",如果要弹出电话键盘就使用type="tel",但这...

前端老手
今天
6
0
总结:Raft协议

一、Raft协议是什么? 分布式一致性算法。即解决分布式系统中各个副本数据一致性问题。 二、Raft的日志广播过程 发送日志到所有Followers(Raft中将非Leader节点称为Follower)。 Followers收...

浮躁的码农
今天
7
0
Flask-admin Model View字段介绍

Model View字段介绍 can_create = True 是否可以创建can_edit = True 是否可以编辑can_delete = True 是否可以删除list_template = 'admin/model/list.html' 修改显......

dillonxiao
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部