元编程(一)

元编程(一)



一、什么是元编程?

百度百科的定义是:元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。很多情况下与手工编写全部代码相比工作效率更高。

元编程通常有两种方式起作用。一种方式是通过应用程序接口(API)来暴露运行时引擎的内部信息。另一种方法是动态执行包含编程命令的字符串。因此,“程序能编写程序”。虽然两种方式都能用,但大多数元编程主要靠其中一种。

接下来我们看一个例子,它使用的是第二种方式,即动态执行包含编程命令的字符:

#!/bin/bash
# metaprogram
echo '#!/bin/bash' > program
for ((I=1; I<=992; I++)) do
echo "echo $I" >> program
done
chmod +x program

这个程序会生成一个新的脚本文件,它将1. 指定脚本用bash解释的语句 2. 992条打印语句 写入到该文件中,这些语句就是其他的程序。

二、Python 元编程

接下来我们看看python中元编程的定义:
元编程的主要目标是创建函数和类,并用它们来操纵代码(比如说修改、生成或者包装已有的代码)。

关于元编程的两种方式对应的特性有:

  1. 应用程序接口暴露运行时引擎的内部信息:包括装饰器、类装饰器以及元类、对象签名
  2. 动态执行包含编程命令的字符串:用exec()来执行代码以及检查函数和类的内部结构
    这里我们主要介绍两种比较主要的元编程方式,装饰器和元类编程。

装饰器

装饰器模式的定义

装饰器(Decorator)模式是一种设计模式,它的定义是:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。

Python装饰器

在Python中装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。

定义一个装饰器

要定义一个装饰器,计算其他函数的运行时间,只需要编写以下代码:

import time
from functools import wraps


def cal_used_time(func):
    """
    Decorator that reports the execution time.
    """
    @wraps(func)  # 需要使用该装饰器去完成一个自定义的装饰器,否则函数的元信息会丢失
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)  # 被装饰函数正常执行
        end = time.time()
        print(func.__name__, end-start)  # 打印执行时间
        return result
    return wrapper  # 返回装饰后的函数


@cal_used_time
def add(x, y):
    return x + y

装饰器的使用方法是一个语法糖,如下写法是等价的:

# 使用装饰器语法糖
@cal_used_time
def add(x, y):
    return x + y

# 等价写法
def add(x, y):
    return x + y
cal_used_time(add)

使用wraps装饰器编写自定义装饰器

编写装饰器时注意需要使用wraps包裹装饰器,否则原函数的元信息会丢失。

打印以下信息:print(add.__name__)

使用wraps时,打印出来的信息是add,如果不使用wraps,那么打印出来的信息将是wrapper,也就是我们定义的装饰器返回的wrapper函数。

那么wraps这个装饰器做了些什么呢?我们可以从源码来看看:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)


def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

       Returns a decorator that invokes update_wrapper() with the decorated
       function as the wrapper argument and the arguments to wraps() as the
       remaining arguments. Default arguments are as for update_wrapper().
       This is a convenience function to simplify applying partial() to
       update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

我们在使用wraps装饰器时,会将原来的函数传递进去(@wraps(func)),作为wrapped参数,然后wraps返回一个带有部分初始参数的update_wrapper,

    @wraps(func)  # 返回update_wrapper,partial会把除了wrapper外的参数全部初始化好
    def wrapper(*args, **kwargs):
        ...
    return wrapper

update_wrapper有四个参数,wrapper是最终返回的装饰完成的函数,wrapped是被装饰函数,而assigned是需要保留的被装饰函数的元信息,它会原封不动地复制到wrapper中,updated则会把wrapped的值更新到wrapper中。经过上一步的操作,返回的update_wrapper实际上已经定义好了wrapped、assigned和updated参数。

返回的update_wrapper会对wrapper函数进行进行装饰,即update_wrapper(wrapper),这里就把wrapper作为参数传到update_wrapper的wrapper形参上,然后构造一个新的wrapper,并返回,这样就把被装饰函数的元信息保留了下来,另外可以通过wrapper.__wrapped__来访问被装饰函数。这就是wraps装饰器的作用。

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        try:
            value = getattr(wrapped, attr)
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value)
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
    # from the wrapped function when updating __dict__
    wrapper.__wrapped__ = wrapped
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

元类

所有类都是type的实例,但是元类还是type的子类,因此可以作为制造类的工厂。具体来说,元类可以通过实现__init__方法定制实例。

type的用法

type的用法一:获取数据是哪种类型 : type(object) -> the object’s type

type的用法二:创建类:type(object_or_name, bases, dict)

怎么使用元类来创建类

当我们在定义类的时候,使用的就是type的用法二,实际上是调用了type的__new__方法,下面的写法是等价的:

class Man:
    def __init__(self, age):
        self.age = age


man_type = type("Man", (object,), {"__init__": Man.__init__})

所以python加载类的时候,是先把属性和方法加载完毕后,再调用type的new方法

当我们给自己定义的类加上元类时,实际上就是把元类从type改为我们自定义的元类:

class CustomMetaClass(type):
    def __new__(cls, clsname, bases, clsdict):
        print("custom meta replace type")
        return type.__new__(cls, clsname, bases, clsdict)

class Man(metaclass=CustomMetaClass)
    def __init__(self, age):
        self.age = age
# 实际上就变成了 man_type = CustomMetaClass("Man", (object,), {"__init__": Man.__init__})

元类的特殊方法:

方法名称参数返回值作用
__call__cls, clsname, bases, clsdict返回一个类调用类时触发
__new__cls, clsname, bases, clsdict返回一个类创建一个新的类
__init__cls, clsname, bases, clsdict无返回值初始化类
__prepare__cls, clsname, bases返回命名空间创建命名空间,即obj.__dict__

正常初始化时调用顺序是:__prepare__ -> __new__ -> __init__prepaere产生的命名空间就是后续的clsdict

三、总结:

本篇作为一个总概篇,介绍了元编程的定义,及其主要的两种方式。因为装饰器和元类编程的内容都挺多的,所以各作为单独的篇章放在后面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海夜风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值