浅谈Python的元编程

目录
  • 一、装饰器
  • 二、装饰器的执行顺序
  • 三、元类
  • 四、descriptor 类(描述符类)
  • 五、总结

相应的元编程就是描述代码本身的代码,元编程就是关于创建操作源代码(比如修改、生成或包装原来的代码)的函数和类。主要技术是使用装饰器、元类、描述符类。

一、装饰器

装饰器就是函数的函数,它接受一个函数作为参数并返回一个新的函数,在不改变原来函数代码的情况下为其增加新的功能,比如最常用的计时装饰器:

from functools import wraps 

def timeit(logger=None):
    """
    耗时统计装饰器,单位是秒,保留 4 位小数
    """ 

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            if logger:
                logger.info(f"{func.__name__} cost {end - start :.4f} seconds")
            else:
                print(f"{func.__name__} cost {end - start :.4f} seconds")
            return result 

        return wrapper 

    return decorator

(注:比如上面使用 @wraps(func) 注解是很重要的, 它能保留原始函数的元数据) 只需要在原来的函数上面加上 @timeit() 即可为其增加新的功能:

@timeit()
def test_timeit():
    time.sleep(1) 

test_timeit()
#test_timeit cost 1.0026 seconds

上面的代码跟下面这样写的效果是一样的:

test_timeit = timeit(test_timeit)
test_timeit()

二、装饰器的执行顺序

当有多个装饰器的时候,他们的调用顺序是怎么样的?

假如有这样的代码,请问是先打印 Decorator1 还是 Decorator2 ?

from functools import wraps 

def decorator1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Decorator 1')
        return func(*args, **kwargs)
    return wrapper 

def decorator2(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Decorator 2')
        return func(*args, **kwargs)
    return wrapper 

@decorator1
@decorator2
def add(x, y):
    return x + y 

add(1,2) 

# Decorator 1
# Decorator 2

回答这个问题之前,我先给你打个形象的比喻,装饰器就像函数在穿衣服,离它最近的最先穿,离得远的最后穿,上例中 decorator1 是外套,decorator2 是内衣。

add = decorator1(decorator2(add))

在调用函数的时候,就像脱衣服,先解除最外面的 decorator1,也就是先打印 Decorator1,执行到 return func(*args, **kwargs) 的时候会去解除 decorator2,然后打印 Decorator2,再次执行到 return func(*args, **kwargs) 时会真正执行 add() 函数。

需要注意的是打印的位置,如果打印字符串的代码位于调用函数之后,像下面这样,那输出的结果正好相反:

def decorator1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print('Decorator 1')
        return result
    return wrapper 

def decorator2(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print('Decorator 2')
        return result
    return wrapper

装饰器不仅可以定义为函数,也可以定义为类,只要你确保它实现了__call__() __get__() 方法。

关于装饰器的其他用法,可以参考前文:

  • 我是装饰器
  • 再谈装饰器

三、元类

Python 中所有类(object)的元类,就是 type 类,也就是说 Python 类的创建行为由默认的 type 类控制,打个比喻,type 类是所有类的祖先。我们可以通过编程的方式来实现自定义的一些对象创建行为。

定一个类继承 type 类 A,然后让其他类的元类指向 A,就可以控制 A 的创建行为。典型的就是使用元类实现一个单例:

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 __init____new__ 方法会在定义 Spam 的期间被执行,而 __call__方法会在实例化 Spam 的时候执行。

如果想更好的理解元类,可以阅读Python黑魔法之metaclass

四、descriptor 类(描述符类)

descriptor 就是任何一个定义了 __get__(),__set__() __delete__()的对象,描述器让对象能够自定义属性查找、存储和删除的操作。这里举官方文档[1]一个自定义验证器的例子。

定义验证器类,它是一个描述符类,同时还是一个抽象类:

from abc import ABC, abstractmethod 

class Validator(ABC): 

    def __set_name__(self, owner, name):
        self.private_name = '_' + name 

    def __get__(self, obj, objtype=None):
        return getattr(obj, self.private_name) 

    def __set__(self, obj, value):
        self.validate(value)
        setattr(obj, self.private_name, value) 

    @abstractmethod
    def validate(self, value):
        pass

自定义验证器需要从 Validator 继承,并且必须提供 validate() 方法以根据需要测试各种约束。

这是三个实用的数据验证工具:

OneOf 验证值是一组受约束的选项之一

class OneOf(Validator): 

    def __init__(self, *options):
        self.options = set(options) 

    def validate(self, value):
        if value not in self.options:
            raise ValueError(f'Expected {value!r} to be one of {self.options!r}')

Number 验证值是否为 int 或 float。根据可选参数,它还可以验证值在给定的最小值或最大值之间。

class Number(Validator): 

    def __init__(self, minvalue=None, maxvalue=None):
        self.minvalue = minvalue
        self.maxvalue = maxvalue 

    def validate(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError(f'Expected {value!r} to be an int or float')
        if self.minvalue is not None and value < self.minvalue:
            raise ValueError(
                f'Expected {value!r} to be at least {self.minvalue!r}'
            )
        if self.maxvalue is not None and value > self.maxvalue:
            raise ValueError(
                f'Expected {value!r} to be no more than {self.maxvalue!r}'
            )

String 验证值是否为 str。根据可选参数,它可以验证给定的最小或最大长度。它还可以验证用户定义的 predicate

class String(Validator): 

    def __init__(self, minsize=None, maxsize=None, predicate=None):
        self.minsize = minsize
        self.maxsize = maxsize
        self.predicate = predicate 

    def validate(self, value):
        if not isinstance(value, str):
            raise TypeError(f'Expected {value!r} to be an str')
        if self.minsize is not None and len(value) < self.minsize:
            raise ValueError(
                f'Expected {value!r} to be no smaller than {self.minsize!r}'
            )
        if self.maxsize is not None and len(value) > self.maxsize:
            raise ValueError(
                f'Expected {value!r} to be no bigger than {self.maxsize!r}'
            )
        if self.predicate is not None and not self.predicate(value):
            raise ValueError(
                f'Expected {self.predicate} to be true for {value!r}'
            )

实际应用时这样写:

class Component: 

    name = String(minsize=3, maxsize=10, predicate=str.isupper)
    kind = OneOf('wood', 'metal', 'plastic')
    quantity = Number(minvalue=0) 

    def __init__(self, name, kind, quantity):
        self.name = name
        self.kind = kind
        self.quantity = quantity

描述器阻止无效实例的创建:

>>> Component('Widget', 'metal', 5)      # Blocked: 'Widget' is not all uppercase
Traceback (most recent call last):
    ...
ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget' 

>>> Component('WIDGET', 'metle', 5)      # Blocked: 'metle' is misspelled
Traceback (most recent call last):
    ...
ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'} 

>>> Component('WIDGET', 'metal', -5)     # Blocked: -5 is negative
Traceback (most recent call last):
    ...
ValueError: Expected -5 to be at least 0
>>> Component('WIDGET', 'metal', 'V')    # Blocked: 'V' isn't a number
Traceback (most recent call last):
    ...
TypeError: Expected 'V' to be an int or float 

>>> c = Component('WIDGET', 'metal', 5)  # Allowed:  The inputs are valid

五、总结

关于 Python 的元编程,总结如下:

如果希望某些函数拥有相同的功能,希望不改变原有的调用方式、不写重复代码、易维护,可以使用装饰器来实现。

如果希望某一些类拥有某些相同的特性,或者在类定义实现对其的控制,我们可以自定义一个元类,然后让它类的元类指向该类。

如果希望实例的属性拥有某些共同的特点,就可以自定义一个描述符类。

到此这篇关于浅谈Python的元编程的文章就介绍到这了,更多相关Python的元编程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-09-14

Python中用Decorator来简化元编程的教程

少劳多得 Decorator 与 Python 之前引入的元编程抽象有着某些共同之处:即使没有这些技术,您也一样可以实现它们所提供的功能.正如 Michele Simionato 和我在 可爱的 Python 专栏的早期文章 中指出的那样,即使在 Python 1.5 中,也可以实现 Python 类的创建,而不需要使用 "元类" 挂钩. Decorator 根本上的平庸与之非常类似.Decorator 所实现的功能就是修改紧接 Decorator 之后定义的函数和方法.这总是可能的,

python中使用 unittest.TestCase单元测试的用例详解

目录 单元测试和测试用例 各种断言方法 函数测试 1.准备测试函数 2.编写一个能使用它的程序 3.对函数进行单元测试 类测试 1.准备测试的类 2.编写一个能使用它的程序 3.对类进行单元测试 注意 单元测试和测试用例 python标准库中的模块unittest提供了代码测试工具.单元测试用于核实函数的莫个方面没有问题:测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求.良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试.全覆盖测试用例包含一整套

Python BeautifulSoup基本用法详解(通过标签及class定位元素)

如下: 将html文件下载后,使用BeauifulSoup读取文件,并且使用html.parser tmp_soup.select里面的参数为: div标签中class中带有listbg 下面 span标签中带有title,这种意思: 并且他们的类型如下: 都是ResultSet类型. 可以通过下面这种方式获取, find('某个标签')['中包含的域'] 当为li标签的时候,可以通过这样的方式获取: 到此这篇关于Python BeautifulSoup基本用法(通过标签及class定位元素)的

python元组打包和解包过程详解

1.在将多个以逗号分隔的值赋给一个变量时,多个值被打包成一个元组类型.当我们将一个元组赋给多个变量时,它将解包成多个值,然后分别将其赋给相应的变量. # 打包 a = 1, 10, 100 print(type(a), a) # <class 'tuple'> (1, 10, 100) # 解包 i, j, k = a print(i, j, k) # 1 10 100 2.解包时,如果解包出来的元素数目与变量数目不匹配,就会引发ValueError异常.错误信息为:too many valu

python使用xpath获取页面元素的使用

关于python 使用xpath获取网页信息的方法? 1.xpath的使用方法? ​ XPath 使用路径表达式来选取 XML 文档中的节点或节点集.节点是通过沿着路径 (path) 或者步 (steps) 来选取的. 常用路径表达式含义 表达式 描述 / 从根节点选取(取子节点) // 选择的当前节点选择文档中的节点 . 选取当前节点. - 选取当前节点的父节点. @ 选取属性 * 表示任意内容(通配符) | 运算符可以选取多个路径 常用功能函数 函数 用法 解释 startswith() x

Python基础之元编程知识总结

一.前言 首先说,Python中一切皆对象,老生常谈.还有,Python提供了许多特殊方法.元类等等这样的"元编程"机制.像给对象动态添加属性方法之类的,在Python中根本谈不上是"元编程",但在某些静态语言中却是需要一定技巧的东西.我们来谈些Python程序员也容易被搞糊涂的东西. 我们先来把对象分分层次,通常我们知道一个对象有它的类型,老早以前Python就将类型也实现为对象.这样我们就有了实例对象和类对象.这是两个层次.稍有基础的读者就会知道还有元类这个东西

python一绘制元二次方程曲线的实例分析

说明 1.Matplotlib函数可以绘制图形,使用plot函数绘制曲线. 2.需要将200个点的x坐标和Y坐标分别以序列的形式输入plot函数,然后调用show函数来显示图形. 实例 import matplotlib.pyplot as plt #200个点的x坐标 x=range(-100,100) #生成y点的坐标 y=[i**2 for i in x ] #绘制一元二次曲线 plt.plot(x,y) #调用savefig将一元二次曲线保存为result.jpg plt.savefig

如何利用Python批量处理行、列和单元格详解

目录 精确调整工作表的行高和列宽 批量更改多个工作簿的数据格式 批量更改工作簿的外观格式 设置字体格式 修改字体为宋体 修改字号 字体加粗 字体颜色 单元格填充颜色 对齐方式 添加合适粗细的边框 替换工作簿的行数据 提取指定数据 提取列数据 追加行数据 提取所有工作表的唯一值 总结 精确调整工作表的行高和列宽 步骤 打开工作簿. 遍历所有工作表 核心代码 for i in workbook.sheets: value = i.range('A1').expand('table') value.c

python自动化八大定位元素讲解

一.find_element_by_id() find_element_by_id() 1.从上面定位到的元素属性中,可以看到有个id属性:id="kw",这里可以通过它的id属性定位到这个元素. 2.定位到搜索框后,用send_keys()方法,就可以输入文本. from selenium import webdriver driver = webdriver.Firefox() driver.get("http://www.baidu.com") # 通过id定

Python接口自动化浅析unittest单元测试原理

目录 一.单元测试三连问 1.什么是单元测试? 2.为什么要做单元测试? 3.怎么做单元测试? 二.unittest模块说明 1.unittest简介 2.unittest组成 1.TestCase(测试用例): 2.TestSuite(测试套件): 3.TestLoader(测试用例加载器): 4.TextTestRunner(执行测试用例): 5.Test Fixture(测试环境数据准备和清理): 3.unittest核心工作原理 三.unittest单元测试 1.实现思路 2.使用介绍

详解Python自动化中这八大元素定位

一.find_element_by_id() find_element_by_id() 1.从上面定位到的元素属性中,可以看到有个id属性:id="kw",这里可以通过它的id属性定位到这个元素. 2.定位到搜索框后,用send_keys()方法,就可以输入文本. from selenium import webdriver driver = webdriver.Firefox() driver.get("http://www.baidu.com") # 通过id定

Python元类与迭代器生成器案例详解

1.__getattr__和__getattribute__魔法函数 __getattr__是当类调用一个不存在的属性时才会调用getattr魔法函数,他传入的值item就是你这个调用的不存在的值. __getattribute__则是无条件的优先执行,所以如果不是特殊情况最好不要用__getattribute__. class User(object): def __init__(self, name, info): self.name = name self.info = info def

Python中使用装饰器和元编程实现结构体类实例

Ruby中有一个很方便的Struct类,用来实现结构体.这样就不用费力的去定义一个完整的类来仅仅用作访问属性. 复制代码 代码如下: class Dog < Struct.new(:name, :age) end fred = Dog.new("fred", 5) printf "name:%s age:%d", fred.name, fred.age ##name:fred age:5 Python3.4中也可以这么干,但写法很累赘.其中包含self.nam

简析Python函数式编程字符串和元组及函数分类与高阶函数

目录 函数式编程中的字符串 不变类型元组 普通元组的声明与访问 函数式的分类 any() .all() .len().sum() 对比学习 zip().reversed().enumerate() 高阶函数 函数 max 和 min() map 函数 filter 函数 sorted 函数 同一需求的不同效率问题 函数式编程中的字符串 在函数式编程中,经常用到 Python 字符串,因其是不可变数据结构. 字符串本身是一个对象,具备很多对象方法,与常识中函数的使用不太相同,例如下述代码 my_s

JavaScript函数式编程(Functional Programming)高阶函数(Higher order functions)用法分析

本文实例讲述了JavaScript函数式编程(Functional Programming)高阶函数(Higher order functions)用法.分享给大家供大家参考,具体如下: 高阶函数(higher-order functions),就是返回其它函数的函数,或者使用其它函数作为它的参数的函数. 使用函数作为参数 因为函数本身就是一个值,所以可以让函数作为参数传递给其它的函数.JavaScript 有些函数就需要用到函数类型的参数,比如 Array.map. 比如我有一组数据: con

Python的函数的一些高阶特性

高阶函数英文叫Higher-order function.什么是高阶函数?我们以实际代码为例子,一步一步深入概念. 变量可以指向函数 以Python内置的求绝对值的函数abs()为例,调用该函数用以下代码: >>> abs(-10) 10 但是,如果只写abs呢? >>> abs <built-in function abs> 可见,abs(-10)是函数调用,而abs是函数本身. 要获得函数调用结果,我们可以把结果赋值给变量: >>> x

详解python内置常用高阶函数(列出了5个常用的)

高阶函数是在Python中一个非常有用的功能函数,所谓高阶函数就是一个函数可以用来接收另一个函数作为参数,这样的函数叫做高阶函数. python内置常用高阶函数: 一.函数式编程 •函数本身可以赋值给变量,赋值后变量为函数: •允许将函数本身作为参数传入另一个函数: •允许返回一个函数. 1.map()函数 是 Python 内置的高阶函数,它接收一个函数 f 和一个 list, 并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回 def add(x): ret

详解Python高阶函数

本文要点 1.什么是高阶函数 2.python中有哪些常用的高阶函数 什么是高阶函数? 在了解什么是高阶函数之前,我们来看几个小例子.我们都知道在 python 中一切皆对象,函数也不例外.比如求绝对值函数 abs,我们可以用一个变量 f 指向 abs 函数,那么当调用 f() 的时候可以得到和 abs() 一样的效果,这说明变量可以指向函数! 同理我们将 abs 指向另一个函数 abs = len,那么 abs 将不再是求绝对值的函数了,abs指向的是求长度的 len 函数.这说明函数名其实就

Python函数式编程指南(一):函数式编程概述

1. 函数式编程概述 1.1. 什么是函数式编程? 函数式编程使用一系列的函数解决问题.函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态.任何情况下,使用相同的参数调用函数始终能产生同样的结果. 在一个函数式的程序中,输入的数据"流过"一系列的函数,每一个函数根据它的输入产生输出.函数式风格避免编写有"边界效应"(side effects)的函数:修改内部状态,或者是其他无法反应在输出上的变化.完全没有边界效应的函数被称为"纯函数式的"

详解Python函数式编程—高阶函数

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用.而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的. 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数! Python对函数式编程提供部分支持.由于Python允许使用变量,因此,Python不是纯函数式编程语言. 高阶函数 变量

Python函数式编程实例详解

本文实例讲述了Python函数式编程.分享给大家供大家参考,具体如下: 函数式编程就是一种抽象程度很高的编程范式,从计算机硬件->汇编语言->C语言->Python抽象程度越高.越贴近于计算,但执行效率也越低.纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用.而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的.函数式编程的一个特点就是,允许把函数

浅析Python函数式编程

Functional Programming,函数式编程.Python对函数式编程提供部分支持.对于纯函数编程,对任一函数,只要输入是确定的,输出就是确定的,可称之为无副作用. 一.高阶函数 1.变量指向函数 我们知道函数的计算结果可以赋值给变量,例如x = abs(-5). 同理,变量也可以指向函数,例如f = abs. 如果一个变量指向了一个函数,那我们直接调用abs(x)与执行f(x)返回的结果是完全相同. 2.函数名也是变量 我们也可以把函数名看成是一个变量,例如abs()函数.执行语句

Python函数式编程指南:对生成器全面讲解

生成器是迭代器,同时也并不仅仅是迭代器,不过迭代器之外的用途实在是不多,所以我们可以大声地说:生成器提供了非常方便的自定义迭代器的途径. 这是函数式编程指南的最后一篇,似乎拖了一个星期才写好,嗯-- 1. 生成器(generator) 1.1. 生成器简介 首先请确信,生成器就是一种迭代器.生成器拥有next方法并且行为与迭代器完全相同,这意味着生成器也可以用于Python的for循环中.另外,对于生成器的特殊语法支持使得编写一个生成器比自定义一个常规的迭代器要简单不少,所以生成器也是最常用到的

浅谈Python 函数式编程

匿名函数lambda表达式 什么是匿名函数? 匿名函数,顾名思义就是没有名字的函数,在程序中不用使用 def 进行定义,可以直接使用 lambda 关键字编写简单的代码逻辑.lambda 本质上是一个函数对象,可以将其赋值给另一个变量,再由该变量来调用函数,也可以直接使用. #平时,我们是先定义函数,再进行调用 def power(x): return x ** 2 print(power(2)) #使用lambda表达式的时候,我们可以这样操作 power = lambda x : x **