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 相关文章推荐
在Django的模型中执行原始SQL查询的方法
Jul 21 Python
Python爬虫辅助利器PyQuery模块的安装使用攻略
Apr 24 Python
关于Python数据结构中字典的心得
Dec 04 Python
python如何把嵌套列表转变成普通列表
Mar 20 Python
解决Django的request.POST获取不到内容的问题
May 28 Python
Python中的函数式编程:不可变的数据结构
Oct 08 Python
python之信息加密题目详解
Jun 26 Python
python获取点击的坐标画图形的方法
Jul 09 Python
Python将文字转成语音并读出来的实例详解
Jul 15 Python
python处理自动化任务之同时批量修改word里面的内容的方法
Aug 23 Python
通过python3实现投票功能代码实例
Sep 26 Python
Python %r和%s区别代码实例解析
Apr 03 Python
Python利用folium实现地图可视化
python爬虫之selenium库的安装及使用教程
教你利用python实现企业微信发送消息
python基础之文件处理知识总结
May 23 #Python
Python绘制地图神器folium的新人入门指南
Python关于OS文件目录处理的实例分享
May 23 #Python
python引入其他文件夹下的py文件具体方法
You might like
PHP自动生成后台导航网址的最佳方法
2013/08/27 PHP
PHP5.5.15+Apache2.4.10+MySQL5.6.20配置方法分享
2016/05/06 PHP
PHP+AJAX 投票器功能
2017/11/11 PHP
javascript 新浪背投广告实现代码
2009/07/07 Javascript
window.onload 加载完毕的问题及解决方案(上)
2009/07/09 Javascript
js 设置选中行的样式的实现代码
2010/05/24 Javascript
基于jQuery的让非HTML5浏览器支持placeholder属性的代码
2011/05/24 Javascript
jQuery Ajax请求状态管理器打包
2012/05/03 Javascript
JS去除右边逗号的简单方法
2013/07/03 Javascript
Node.js实现Excel转JSON
2015/04/24 Javascript
nodejs的HTML分析利器node-jquery用法浅析
2016/11/08 NodeJs
Kendo Grid editing 自定义验证报错提示的解决方法
2016/11/18 Javascript
JavaScript表单验证开发
2016/11/23 Javascript
微信小程序switch组件使用详解
2018/01/31 Javascript
jQuery实现带进度条的轮播图
2020/09/13 jQuery
[06:14]《辉夜杯》外卡赛附加赛 4支战队巡礼
2015/10/23 DOTA
python中Flask框架简单入门实例
2015/03/21 Python
Python通过Django实现用户注册和邮箱验证功能代码
2017/12/11 Python
Python tornado队列示例-一个并发web爬虫代码分享
2018/01/09 Python
python2.6.6如何升级到python2.7.14
2018/04/08 Python
Django 路由系统URLconf的使用
2018/10/11 Python
Python3.4学习笔记之常用操作符,条件分支和循环用法示例
2019/03/01 Python
Python3.6 + TensorFlow 安装配置图文教程(Windows 64 bit)
2020/02/24 Python
CSS3中Color的一些特性介绍
2012/05/27 HTML / CSS
Html5实现文件异步上传功能
2017/05/19 HTML / CSS
Topshop美国官网:英国快速时尚品牌
2019/05/16 全球购物
奥地利票务门户网站:oeticket.com
2019/12/31 全球购物
.NET remoting中对象激活的两种方式
2015/06/08 面试题
护士毕业实习感言
2014/03/05 职场文书
推荐信格式范文
2014/05/09 职场文书
ktv好的活动方案
2014/08/15 职场文书
2015年元旦文艺晚会总结(学院)
2014/11/28 职场文书
2015政治思想表现评语
2015/03/25 职场文书
《平移和旋转》教学反思
2016/02/19 职场文书
PL350与SW11的比较
2021/04/22 无线电
python+opencv实现视频抽帧示例代码
2021/06/11 Python