Python进阶学习之带你探寻Python类的鼻祖-元类


Posted in Python onMay 08, 2021

Python是一门面向对象的语言,所以Python中数字、字符串、列表、集合、字典、函数、类等都是对象。

利用 type() 来查看Python中的各对象类型

In [11]: # 数字

In [12]: type(10)
Out[12]: int

In [13]: type(3.1415926)
Out[13]: float

In [14]: # 字符串

In [15]: type('a')
Out[15]: str

In [16]: type("abc")
Out[16]: str

In [17]: # 列表

In [18]: type(list)
Out[18]: type

In [19]: type([])
Out[19]: list

In [20]: # 集合

In [21]: type(set)
Out[21]: type

In [22]: my_set = {1, 2, 3}

In [23]: type(my_set)
Out[23]: set

In [24]: # 字典

In [25]: type(dict)
Out[25]: type

In [26]: my_dict = {'name': 'hui'}

In [27]: type(my_dict)
Out[27]: dict

In [28]: # 函数

In [29]: def func():
    ...:     pass
    ...:

In [30]: type(func)
Out[30]: function

In [31]: # 类

In [32]: class Foo(object):
    ...:     pass
    ...:

In [33]: type(Foo)
Out[33]: type

In [34]: f = Foo()

In [35]: type(f)
Out[35]: __main__.Foo

In [36]: # type

In [37]: type(type)
Out[37]: type

可以看出

数字 1int类型 的对象

字符串 abcstr类型 的对象

列表、集合、字典是 type类型 的对象,其创建出来的对象才分别属于 list、set、dict 类型

函数 funcfunction类型 的对象

自定义类 Foo 创建出来的对象 fFoo 类型,其类本身 Foo 则是 type类型 的对象。

type 本身都是type类型的对象

一、类也是对象

类就是拥有相等功能和相同的属性的对象的集合

在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在 Python 中这一点仍然成立:

In [1]: class ObjectCreator(object):
   ...:     pass
   ...:

In [2]: my_object = ObjectCreator()

In [3]: print(my_object)
<__main__.ObjectCreator object at 0x0000021257B5A248>

但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你 使用关键字 class,Python解释器在执行的时候就会创建一个对象。

下面的代码段:

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

将在内存中创建一个对象,名字就是 ObjectCreator。这个 对象(类对象ObjectCreator)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:

1.你可以将它赋值给一个变量

2.你可以拷贝它

3.你可以为它增加属性

4.你可以将它作为函数参数进行传递

如下示例:

In [39]: class ObjectCreator(object):
    ...:     pass
    ...:

In [40]: print(ObjectCreator)
<class '__main__.ObjectCreator'>

In [41]:# 当作参数传递

In [41]: def out(obj):
    ...:     print(obj)
    ...:

In [42]: out(ObjectCreator)
<class '__main__.ObjectCreator'>

In [43]: # hasattr 判断一个类是否有某种属性

In [44]: hasattr(ObjectCreator, 'name')
Out[44]: False

In [45]: # 新增类属性

In [46]: ObjectCreator.name = 'hui'

In [47]: hasattr(ObjectCreator, 'name')
Out[47]: True

In [48]: ObjectCreator.name
Out[48]: 'hui'

In [49]: # 将类赋值给变量

In [50]: obj = ObjectCreator

In [51]: obj()
Out[51]: <__main__.ObjectCreator at 0x212596a7248>

In [52]:

二、动态地创建类

因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用 class 关键字即可。

def cls_factory(cls_name):
    """
    创建类工厂
    :param: cls_name 创建类的名称
    """
    if cls_name == 'Foo':
        class Foo():
            pass
        return Foo  # 返回的是类,不是类的实例

    elif cls_name == 'Bar':
        class Bar():
            pass
        return Bar

IPython 测验

MyClass = cls_factory('Foo')

In [60]: MyClass
Out[60]: __main__.cls_factory.<locals>.Foo # 函数返回的是类,不是类的实例

In [61]: MyClass()
Out[61]: <__main__.cls_factory.<locals>.Foo at 0x21258b1a9c8>

但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。

当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。

三、使用 type 创建类

type 还有一种完全不同的功能,动态的创建类。

type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

type 可以像这样工作:

type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

In [63]: class Test:
    ...:     pass
    ...:

In [64]: Test()
Out[64]: <__main__.Test at 0x21258b34048>

In [65]:

可以手动像这样创建:

In [69]:# 使用type定义类

In [69]: Test2 = type('Test2', (), {})

In [70]: Test2()
Out[70]: <__main__.Test2 at 0x21259665808>

我们使用 Test2 作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。即 type函数 中第1个实参,也可以叫做其他的名字,这个名字表示类的名字

In [71]: UserCls = type('User', (), {})

In [72]: print(UserCls)
<class '__main__.User'>

In [73]:

使用 help 来测试这2个类

In [74]: # 用 help 查看 Test类

In [75]: help(Test)
Help on class Test in module __main__:

class Test(builtins.object)
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)


In [76]: # 用 help 查看 Test2类

In [77]: help(Test2)
Help on class Test2 in module __main__:

class Test2(builtins.object)
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)


In [78]:

四、使用type创建带有属性的类

type 接受一个字典来为类定义属性,因此

Parent = type('Parent', (), {'name': 'hui'})

可以翻译为:

class Parent(object):
	name = 'hui'

并且可以将 Parent 当成一个普通的类一样使用:

In [79]: Parent = type('Parent', (), {'name': 'hui'})

In [80]: print(Parent)
<class '__main__.Parent'>

In [81]: Parent.name
Out[81]: 'hui'

In [82]: p = Parent()

In [83]: p.name
Out[83]: 'hui'

当然,你可以继承这个类,代码如下:

class Child1(Parent):
    name = 'jack'
    sex =  '男'
    
class Child2(Parent):
    name = 'mary'
    sex = '女'

就可以写成:

Child1 = type('Child1', (Parent, ), {'name': 'jack', 'sex': '男'})

In [85]: Child2 = type('Child2', (Parent, ), {'name': 'mary', 'sex': '女'})

In [87]: Child1.name, Child1.sex
Out[87]: ('jack', '男')

In [88]: Child2.name, Child2.sex
Out[88]: ('mary', '女')

注意:

  • type 的第2个参数,元组中是父类的名字,而不是字符串
  • 添加的属性是 类属性,并不是实例属性

五、使用type创建带有方法的类

最终你会希望为你的类增加方法。只需要定义一个有着恰当签名的函数并将其作为属性赋值就可以了。

添加实例方法

Child1 = type('Child1', (Parent, ), {'name': 'jack', 'sex': '男'})

In [85]: Child2 = type('Child2', (Parent, ), {'name': 'mary', 'sex': '女'})

In [87]: Child1.name, Child1.sex
Out[87]: ('jack', '男')

In [88]: Child2.name, Child2.sex
Out[88]: ('mary', '女')

添加静态方法

In [96]: Parent = type('Parent', (), {'name': 'hui'})

In [97]: # 定义静态方法
    
In [98]: @staticmethod
    ...: def test_static():
    ...:     print('static method called...')
    ...:

In [100]: Child4 = type('Child4', (Parent, ), {'name': 'zhangsan', 'test_static': test_static})

In [101]: c4 = Child4()

In [102]: c4.test_static()
static method called...

In [103]: Child4.test_static()
static method called...

添加类方法

In [105]: Parent = type('Parent', (), {'name': 'hui'})

In [106]: # 定义类方法

In [107]: @classmethod
     ...: def test_class(cls):
     ...:     print(cls.name)
     ...:

In [108]: Child5 = type('Child5', (Parent, ), {'name': 'lisi', 'test_class': test_class})

In [109]: c5 = Child5()

In [110]: c5.test_class()
lisi

In [111]: Child5.test_class()
lisi

你可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字 classPython 在幕后做的事情,就是通过元类来实现的

较为完整的使用 type 创建类的方式:

class Animal(object):
    
    def eat(self):
        print('吃东西')


def dog_eat(self):
    print('喜欢吃骨头')

def cat_eat(self):
    print('喜欢吃鱼')


Dog = type('Dog', (Animal, ), {'tyep': '哺乳类', 'eat': dog_eat})

Cat = type('Cat', (Animal, ), {'tyep': '哺乳类', 'eat': cat_eat})

# ipython 测验
In [125]: animal = Animal()

In [126]: dog = Dog()

In [127]: cat = Cat()

In [128]: animal.eat()
吃东西

In [129]: dog.eat()
喜欢吃骨头

In [130]: cat.eat()
喜欢吃鱼

六、到底什么是元类(终于到主题了)

元类就是用来创建类的【东西】。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。

元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

MyClass = MetaClass() # 使用元类创建出一个对象,这个对象称为“类”
my_object = MyClass() # 使用“类”来创建出实例对象

你已经看到了type可以让你像这样做:

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

这是因为函数 type 实际上是一个元类。type 就是 Python在背后用来创建所有类的元类。现在你想知道那为什么 type 会全部采用小写形式而不是 Type 呢?好吧,我猜这是为了和 str 保持一致性,str是用来创建字符串对象的类,而 int 是用来创建整数对象的类。type 就是创建类对象的类。你可以通过检查 __class__ 属性来看到这一点。因此 Python中万物皆对象

现在,对于任何一个 __class____class__ 属性又是什么呢?

In [136]: a = 10

In [137]: b = 'acb'

In [138]: li = [1, 2, 3]

In [139]: a.__class__.__class__
Out[139]: type

In [140]: b.__class__.__class__
Out[140]: type

In [141]: li.__class__.__class__
Out[141]: type

In [142]: li.__class__.__class__.__class__
Out[142]: type

因此,元类就是创建类这种对象的东西。type 就是 Python的内建元类,当然了,你也可以创建自己的元类。

七、metaclass属性

你可以在定义一个类的时候为其添加 __metaclass__ 属性。

class Foo(object):
    __metaclass__ = something…
    ...省略...

如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下 class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找 __metaclass__ 属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的 type 来创建这个类。

class Foo(Bar):
    pass

Python做了如下的操作:

1.Foo中有 __metaclass__ 这个属性吗?如果有,Python会通过 __metaclass__ 创建一个名字为Foo的类(对象)

2.如果Python没有找到 __metaclass__,它会继续在 Bar(父类) 中寻找 __metaclass__ 属性,并尝试做和前面同样的操作。

3.如果Python在任何父类中都找不到 __metaclass__,它就会在模块层次中去寻找 __metaclass__,并尝试做同样的操作。

4.如果还是找不到 __metaclass__ ,Python就会用内置的 type 来创建这个类对象。

现在的问题就是,你可以在 __metaclass__ 中放置些什么代码呢?

答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化的type都可以。

八、自定义元类

元类的主要目的就是为了当创建类时能够自动地改变类。

假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定 __metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

幸运的是,__metaclass__ 实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。

python2中

# -*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    # class_name 会保存类的名字 Foo
    # class_parents 会保存类的父类 object
    # class_attr 会以字典的方式保存所有的类属性

    # 遍历属性字典,把不是__开头的属性名字变为大写
    new_attr = {}
    for name, value in class_attr.items():
        if not name.startswith("__"):
            new_attr[name.upper()] = value

    # 调用type来创建一个类
    return type(class_name, class_parents, new_attr)

class Foo(object):
    __metaclass__ = upper_attr # 设置Foo类的元类为upper_attr
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Flase
print(hasattr(Foo, 'BAR'))
# True

f = Foo()
print(f.BAR)

python3中

# -*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    #遍历属性字典,把不是__开头的属性名字变为大写
    new_attr = {}
    for name,value in class_attr.items():
        if not name.startswith("__"):
            new_attr[name.upper()] = value

    #调用type来创建一个类
    return type(class_name, class_parents, new_attr)

# 再类的继承()中使用metaclass
class Foo(object, metaclass=upper_attr):
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Flase
print(hasattr(Foo, 'BAR'))
# True

f = Foo()
print(f.BAR)

再做一次,这一次用一个真正的 class 来当做元类。

class UpperAttrMetaClass(type):
    
    def __new__(cls, class_name, class_parents, class_attr):
        # 遍历属性字典,把不是__开头的属性名字变为大写
        new_attr = {}
        for name, value in class_attr.items():
            if not name.startswith("__"):
                new_attr[name.upper()] = value

        # 方法1:通过'type'来做类对象的创建
        return type(class_name, class_parents, new_attr)

        # 方法2:复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        # return type.__new__(cls, class_name, class_parents, new_attr)

        
# python3的用法
class Foo(object, metaclass=UpperAttrMetaClass):
    bar = 'bip'

# python2的用法
class Foo(object):
	__metaclass__ = UpperAttrMetaClass
    bar = 'bip'


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

f = Foo()
print(f.BAR)
# 输出: 'bip'
__new__ 是在__init__之前被调用的特殊方法
__new__是用来创建对象并返回之的方法
而__init__只是用来将传入的参数初始化给对象
这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__

就是这样,除此之外,关于元类真的没有别的可说的了。但就元类本身而言,它们其实是很简单的:

1.拦截类的创建

2.修改类

3.返回修改之后的类

总结

现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?

好吧,一般来说,你根本就用不上它:

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters

到此这篇关于Python进阶学习之带你探寻Python类的鼻祖-元类的文章就介绍到这了,更多相关Python元类内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python解析最简单的验证码
Jan 07 Python
利用Python读取文件的四种不同方法比对
May 18 Python
Python 自动化表单提交实例代码
Jun 08 Python
Django admin实现图书管理系统菜鸟级教程完整实例
Dec 12 Python
Python查看微信撤回消息代码
Jun 07 Python
Python 新建文件夹与复制文件夹内所有内容的方法
Oct 27 Python
Python求离散序列导数的示例
Jul 10 Python
python中的itertools的使用详解
Jan 13 Python
Jupyter Notebook输出矢量图实例
Apr 14 Python
如何快速一次性卸载所有python包(第三方库)呢
Oct 20 Python
如何在向量化NumPy数组上进行移动窗口
May 18 Python
python​格式化字符串
Apr 20 Python
python实战之用emoji表情生成文字
May 08 #Python
python实现过滤敏感词
Django中的JWT身份验证的实现
May 07 #Python
python开发实时可视化仪表盘的示例
Python使用scapy模块发包收包
如何用 Python 子进程关闭 Excel 自动化中的弹窗
PyTorch的Debug指南
May 07 #Python
You might like
php Xdebug 调试扩展的安装与使用.
2010/03/13 PHP
深入解析PHP内存管理之谁动了我的内存
2013/06/20 PHP
PHP在引号前面添加反斜杠(PHP去除反斜杠)
2013/09/28 PHP
2个自定义的PHP in_array 函数,解决大量数据判断in_array的效率问题
2014/04/08 PHP
PHP生成自适应大小的缩略图类及使用方法分享
2014/05/06 PHP
Zend Framework入门之环境配置及第一个Hello World示例(附demo源码下载)
2016/03/21 PHP
laravel框架查询数据集转为数组的两种方法
2019/10/10 PHP
javascript下string.format函数补充
2010/08/24 Javascript
jQuery 追加元素的方法如append、prepend、before
2014/01/16 Javascript
JavaScript数组去重的3种方法和代码实例
2015/07/01 Javascript
AngularJS中处理多个promise的方式
2016/02/02 Javascript
巧用canvas
2017/01/21 Javascript
Node.JS更改Windows注册表Regedit的方法小结
2017/08/18 Javascript
VUE 实现滚动监听 导航栏置顶的方法
2018/09/11 Javascript
angularJs中$scope数据序列化的实例
2018/09/30 Javascript
微信小程序常用赋值方法小结
2019/04/30 Javascript
jQuery实现的点击显示隐藏下拉菜单功能完整示例
2019/05/17 jQuery
如何用原生js写一个弹窗消息提醒插件
2019/05/24 Javascript
JavaScript实现答题评分功能页面
2020/06/24 Javascript
Python转码问题的解决方法
2008/10/07 Python
Python 自动安装 Rising 杀毒软件
2009/04/24 Python
python time模块用法实例详解
2014/09/11 Python
Python多进程同步Lock、Semaphore、Event实例
2014/11/21 Python
Python中List.count()方法的使用教程
2015/05/20 Python
python 自动重连wifi windows的方法
2018/12/18 Python
Django框架模板的使用方法示例
2019/05/25 Python
Python完成毫秒级抢淘宝大单功能
2019/06/06 Python
python实现两个经纬度点之间的距离和方位角的方法
2019/07/05 Python
python正则爬取某段子网站前20页段子(request库)过程解析
2019/08/10 Python
基于python及pytorch中乘法的使用详解
2019/12/27 Python
Python如何绘制日历图和热力图
2020/08/07 Python
html5实现输入框fixed定位在屏幕最底部兼容性
2020/07/03 HTML / CSS
Spotahome意大利:公寓和房间出租
2020/02/21 全球购物
公益活动邀请函
2014/02/05 职场文书
医院信息公开实施方案
2014/05/09 职场文书
Nginx 502 bad gateway错误解决的九种方案及原因
2022/08/14 Servers