Python基础之元编程知识总结


Posted in Python onMay 23, 2021

一、前言

首先说,Python中一切皆对象,老生常谈。还有,Python提供了许多特殊方法、元类等等这样的“元编程”机制。像给对象动态添加属性方法之类的,在Python中根本谈不上是“元编程”,但在某些静态语言中却是需要一定技巧的东西。我们来谈些Python程序员也容易被搞糊涂的东西。

我们先来把对象分分层次,通常我们知道一个对象有它的类型,老早以前Python就将类型也实现为对象。这样我们就有了实例对象和类对象。这是两个层次。稍有基础的读者就会知道还有元类这个东西的存在,简言之,元类就是“类”的“类”,也就是比类更高层次的东西。这又有了一个层次。还有吗?

二、ImportTime vs RunTime

如果我们换个角度,不用非得和之前的三个层次使用同样的标准。我们再来区分两个东西:ImportTime和RunTime,它们之间也并非界限分明,顾名思义,就是两个时刻,导入时和运行时。

当一个模块被导入时,会发生什么?在全局作用域的语句(非定义性语句)被执行。函数定义呢?一个函数对象被创建,但其中的代码不会被执行。类定义呢?一个类对象被创建,类定义域的代码被执行,类的方法中的代码自然也不会被执行。

执行时呢?函数和方法中的代码会被执行。当然你要先调用它们。

三、元类

所以我们可以说元类和类是属于ImportTime的,import一个模块之后,它们就会被创建。实例对象属于RunTime,单import是不会创建实例对象的。不过话不能说的太绝对,因为如果你要是在模块作用域实例化类,实例对象也是会被创建的。只不过我们通常把它们写在函数里面,所以这样划分。

如果你想控制产生的实例对象的特性该怎么做?太简单了,在类定义中重写__init__方法。那么我们要控制类的一些性质呢?有这种需求吗?当然有!

经典的单例模式,大家都知道有很多种实现方式。要求就是,一个类只能有一个实例。

最简单的实现方法是这样的

class _Spam:
    def __init__(self):
        print("Spam!!!")

_spam_singleton =None

def Spam():
    global _spam_singleton
    if _spam_singleton is not None:
        return _spam_singleton
    else:
        _spam_singleton = _Spam()
        return _spam_singleton

工厂模式,不太优雅。我们再来审视一下需求,要一个类只能有一个实例。我们在类中定义的方法都是实例对象的行为,那么要想改变类的行为,就需要更高层次的东西。元类在这个时候登场在合适不过了。前面说过,元类是类的类。也就是说,元类的__init__方法就是类的初始化方法。 我们知道还有__call__这个东西,它能让实例像函数那样被调用,那么元类的这个方法就是类在被实例化时调用的方法。

代码就可以写出来了:

class Singleton(type):
    def __init__(self, *args, **kwargs):
        self._instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super().__call__(*args, **kwargs)
            return self._instance
        else:
            return self._instance


class Spam(metaclass=Singleton):
    def __init__(self):
        print("Spam!!!")

主要有两个地方和一般的类定义不同,一是Singleton的基类是type,一是Spam定义的地方有一个metaclass=Singleton。type是什么?它是object的子类,object是它的实例。也就是说,type是所有类的类,也就是最基本的元类,它规定了一些所有类在产生时需要的一些操作。所以我们的自定义元类需要子类化type。同时type也是一个对象,所以它又是object的子类。有点不太好理解,大概知道就可以了。

四、装饰器

我们再来说说装饰器。大多数人认为装饰器是Python里面最难理解的概念之一。其实它不过就是一个语法糖,理解了函数也是对象之后。就可以很轻易的写出自己的装饰器了。

from functools import wraps

def print_result(func):

    @wraps(func)
    def wrappper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(result)
        return result

    return wrappper

@print_result
def add(x, y):
    return x + y
#相当于:
#add = print_result(add)

add(1, 3)

这里我们还用到了一个装饰器@wraps,它是用来让我们返回的内部函数wrapper和原来的函数拥有相同的函数签名的,基本上我们在写装饰器时都要加上它。

在注释里写了,@decorator这样的形式等价于func=decorator(func),理解了这一点,我们就可以写出更多种类的装饰器。比如类装饰器,以及将装饰器写成一个类。

def attr_upper(cls):
    for attrname,value in cls.__dict__.items():
        if isinstance(value,str):
            if not value.startswith('__'):
                setattr(cls,attrname,bytes.decode(str.encode(value).upper()))
    return cls    

@attr_upper
class Person:
    sex = 'man'

print(Person.sex) # MAN

注意普通的装饰器和类装饰器实现的不同点。

五、对数据的抽象?描述符

如果我们想让某一些类拥有某些相同的特性,或者说可以实现在类定义对其的控制,我们可以自定义一个元类,然后让它成为这些类的元类。如果我们想让某一些函数拥有某些相同的功能,又不想把代码复制粘贴一遍,我们可以定义一个装饰器。那么,假如我们想让实例的属性拥有某些共同的特点呢?有人可能会说可以用property,当然可以。但是这些逻辑必须在每个类定义的时候都写一遍。如果我们想让这些类的实例的某些属性都有相同的特点的话,就可以自定义一个描述符类。

这里我们给出一些例子

class TypedField:
    def __init__(self, _type):
        self._type = _type

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return getattr(instance, self.name)

    def __set_name__(self, cls, name):
        self.name = name

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError('Expected' + str(self._type))
        instance.__dict__[self.name] = value

class Person:
    age = TypedField(int)
    name = TypedField(str)

    def __init__(self, age, name):
        self.age = age
        self.name = name

jack = Person(15, 'Jack')
jack.age = '15'  # 会报错

在这里面有几个角色,TypedField是一个描述符类,的属性Person是描述符类的实例,看似描述符是作为Person,也就是类的属性而不是实例属性存在的。但实际上,一旦Person的实例访问了同名的属性,描述符就会起作用。需要注意的是,在Python3.5及之前的版本中,是没有__set_name__这个特殊方法的,这意味着如果你想要知道在类定义中描述符被起了一个什么样的名字,是需要在描述符实例化时显式传递给它的,也就是需要多一个参数。不过在Python3.6中,这个问题得到了解决,只需要在描述符类定义中重写__set_name__这个方法就好了。还需要注意的是__get__的写法,基本上对instance的判断是必需的,不然会报错。原因也不难理解,就不细说了。

六、控制子类的创建——代替元类的方法

在Python3.6中,我们可以通过实现__init_subclass__特殊方法,来自定义子类的创建,这样我们就可以在某些情况下摆脱元类这个讨厌的东西。

class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

小结诸如元类等元编程对于大多数人来说有些晦涩难懂,大多数时候也无需用到它们。但是大多数框架背后的实现都使用到了这些技巧,这样才能让使用者写出来的代码简洁易懂。如果你想更深入的了解这些技巧,可以参看一些书籍例如《Fluent Python》、《Python Cookbook》(这篇文章有的内容就是参考了它们),或者看官方文档中的某些章节例如上文说的描述符HowTo,还有Data Model一节等等。或者直接看Python的源码,包括用Python写的以及CPython的源码。

记住,只有在充分理解了它们之后再去使用,也不要是个地方就想着使用这些技巧。

到此这篇关于Python基础之元编程知识总结的文章就介绍到这了,更多相关Python元编程内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
使用Python的Flask框架构建大型Web应用程序的结构示例
Jun 04 Python
Python极简代码实现杨辉三角示例代码
Nov 15 Python
Python将多个excel表格合并为一个表格
Feb 22 Python
python使用代理ip访问网站的实例
May 07 Python
python清除函数占用的内存方法
Jun 25 Python
Python图像处理之gif动态图的解析与合成操作详解
Dec 30 Python
使用Python的SymPy库解决数学运算问题的方法
Mar 27 Python
Python Web框架之Django框架Model基础详解
Aug 16 Python
Python将list元素转存为CSV文件的实现
Nov 16 Python
Python爬虫之App爬虫视频下载的实现
Dec 08 Python
python 自动识别并连接串口的实现
Jan 19 Python
如何用Django处理gzip数据流
Jan 29 Python
Python利用folium实现地图可视化
python爬虫之selenium库的安装及使用教程
教你利用python实现企业微信发送消息
python基础之文件处理知识总结
May 23 #Python
Python绘制地图神器folium的新人入门指南
Python关于OS文件目录处理的实例分享
May 23 #Python
python引入其他文件夹下的py文件具体方法
You might like
腾讯QQ php程序员面试题目整理
2010/06/08 PHP
php获取YouTube视频信息的方法
2015/02/11 PHP
PHP连接操作access数据库实例
2015/03/30 PHP
原型方法的不同写法居然会影响调试的解决方法
2007/03/08 Javascript
从零开始学习jQuery (六) jquery中的AJAX使用
2011/02/23 Javascript
js实现一个省市区三级联动选择框代码分享
2013/03/06 Javascript
Javascript控制页面链接在新窗口打开具体方法
2013/08/16 Javascript
多个jQuery版本共存的处理方案
2015/03/17 Javascript
Bootstrap入门书籍之(零)Bootstrap简介
2016/02/17 Javascript
[原创]JQuery 在表单提交之前修改 提交的值
2016/04/14 Javascript
jquery自适应布局的简单实例
2016/05/28 Javascript
jquery层级选择器(匹配父元素下的子元素实现代码)
2016/09/05 Javascript
jQuery的ajax中使用FormData实现页面无刷新上传功能
2017/01/16 Javascript
微信小程序之picker日期和时间选择器
2017/02/09 Javascript
addeventlistener监听scroll跟touch(实例讲解)
2017/08/04 Javascript
jquery 输入框查找关键字并提亮颜色的实例代码
2018/01/23 jQuery
Vue项目开发常见问题和解决方案总结
2020/09/11 Javascript
[00:23]DOTA2群星共贺开放测试 25日无码时代来袭
2013/09/23 DOTA
[55:16]Mski vs VGJ.S Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
[02:38]DOTA2 夜魇暗潮2020活动介绍官方视频
2020/11/04 DOTA
Django数据库表反向生成实例解析
2018/02/06 Python
python利用requests库进行接口测试的方法详解
2018/07/06 Python
Python类和对象的定义与实际应用案例分析
2018/12/27 Python
详解Python匿名函数(lambda函数)
2019/04/19 Python
python plotly绘制直方图实例详解
2019/07/22 Python
TensorFlow保存TensorBoard图像操作
2020/06/23 Python
Python Pygame实现俄罗斯方块
2021/02/19 Python
css3动画过渡实现鼠标跟随导航效果
2018/02/08 HTML / CSS
HTML5: Web 标准最巨大的飞跃
2008/10/17 HTML / CSS
大学生自我鉴定书
2014/03/24 职场文书
村级换届选举方案
2014/05/10 职场文书
导师就业推荐信范文
2014/05/22 职场文书
学校捐书活动总结
2015/05/08 职场文书
班级班风口号大全
2015/12/25 职场文书
python 如何获取页面所有a标签下href的值
2021/05/06 Python
用python删除文件夹中的重复图片(图片去重)
2021/05/12 Python