我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Python中的元编程

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Python中的元编程

就像元数据是关于数据的数据一样,元编程是编写程序来操作程序(Just like metadata is data about data, metaprogramming is writing programs that manipulate programs)。一个常见的看法是元编程是用来成成其他程序的程序,但是实际上它的含义更为广泛(It's a common perception that metaprograms are the programs that generate other programs. But the paradigm is even broader)。所有用于读取、分析、转换或修改自身的程序都是元编程的例子。比如:

  • Domain-specific languages (DSLs)
  • Parsers
  • Interpreters
  • Compilers
  • Theorem provers
  • Term rewriters

这篇教程介绍Python中的元编程,它通过对Python特性的回顾来更新您的Python知识,这样您就可以更好地理解本文中的概念。本文也解释了Python中的type函数除了返回一个对象(上层的)的类之外是如何拥有更重要的意义的。然后,讨论了在Python中元编程的方法以及元编程如何简化某些特定类型的任务。

一点自我反省

如果你已经由一些Python编程经历,你可能知道那句话:Python中一切皆对象,类创建对象。但是如果一切皆对象(则类也是对象),那么是谁创建了类呢?这正是我要回答的问题。

我们来验证一下前面的说法是否正确

>>> class SomeClass:
...     pass
>>> some_object = SomeClass()
>>> type(some_obj)
<__main__.SomeClass instance at 0x7f8de4432f80>

可见,type()函数作用于一个对象时,返回这个对象的类(即该对象由哪个类创建)

>>> import inspect
>>>inspect.isclass(SomeClass)
True
>>>inspect.isclass(some_object)
False
>>>inspect.isclass(type(some_object))
True

inspect.isclass函数返回True如果传给它一个类,对于其他类型返回False。因为some_object不是类(它是类的一个实例),所以 inspect.isclass() 返回False。而type(some_object)返回了创建 some_object 的类,因此inspect.isclass(type(some_object))返回True:

>>> type(SomeClass)
<type 'classobj'>>>>
inspect.isclass(type(SomeClass))
True

classobj是一个特殊的类,在Python3中所有的类都默认继承自它。现在一切变得有道理了,但是 classobj 呢,对它调用type()又会如何呢?

>>> type(type(SomeClass))
<type 'type'>
>>>inspect.isclass(type(type(SomeClass)))
True
>>>type(type(type(SomeClass)))
<type 'type'>
>>>inspect.isclass(type(type(type(SomeClass))))
True

有点意思是么?再来看那个关于Python的名言(一切皆对象)好像并不是那么精确,这样说可能会更好:
Python中除了type以外一切皆对象,他们要么是类的对象,要么是元类的对象。

来验证这个观点:

>>> some_obj = SomeClass()
>>> isinstance(some_obj,SomeClass)
True
>>> isinstance(SomeClass, type)
True

因此我们可以知道实例是一个类的实例化,而类是一个元类的实例化。

type并不是我们以为的那样

type 本身就是一个类,并且它是他自己的 type,它是一个元类。元类可以实例化为类并且定义类的行为,就像类可以实例化为对象并且定义对象的行为一样。

type 是 Python 中一个内建的元类,来控制Python中类的行为,我们可以通过继承自 type 来自定义一个元类。元类是Python中进行元编程的途径。

定义一个类时发生了什么

让我们先复习一下我们已知的知识,在Python中构成代码的基本单元有:

  • Statements
  • Functions
  • Classes

在代码中由 Statements 来完成实际的工作,Statements 可以在全局范围(module level)或是本地范围(within a function)。函数是包含一条或多条语句,用来执行特定任务的,可复用的代码单元。函数同样可以定义在全局范围或本地范围,也可以作为类的方法。类提供了“面向对象编程”的能力,类定义了对象如何被实例化以及他们实例化后将会拥有的属性和方法。

类的命名空间存储于字典中,例如

>>> class SomeClass:
...     class_var = 1
...     def __init__(self):
...         self.some_var = 'Some value'

>>> SomeClass.__dict__
{'__doc__': None,
 '__init__': <function __main__.__init__>,
 '__module__': '__main__',
 'class_var': 1}

>>> s = SomeClass()

>>> s.__dict__
{'some_var': 'Some value'}

下面详细介绍下当遇到class关键字时,会发生什么:

  • 类的主体(语句和函数)被隔离(The body (statements and functions) of the class is isolated.)
  • 类的命名空间字典被创建(但是还未向字典中添加键值对)
  • 类中的代码开始执行,然后代码中定义的所有属性和方法以及一些其他信息(如'__doc__')被添加到命名空间字典中
  • 将要被创建的这个类的元类被识别(这里是简译了,请看原句)(The metaclass is identified in the base classes or the metaclass hooks (explained later) of the class to be created)
  • The metaclass is then called with the name, bases, and attributes of the class to instantiate(实例化) it

由于 type 是Python中默认的元类,所以你可以用 type 去创建类。

type的另一面

type(),当只跟一个参数时,产生现有类的类型信息(produces the type information of an existing class)。当 type() 跟三个参数时,它创建一个新的类对象(type called with three arguments creates a new class object)。三个参数分别是:要创建的类的名称,一个包含基类(父类)的列表,和一个表示类命名空间的字典。

因此

class SomeClass: pass

等价于

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

并且

class ParentClass:
    pass

class SomeClass(ParentClass):
    some_var = 5
    def some_function(self):
        print("Hello!")

等价于

def some_function(self):
    print("Hello")
ParentClass = type('ParentClass', (), {})
SomeClass = type('SomeClass',
                 [ParentClass],
                 {'some_function': some_function,
                  'some_var':5})

因此,通过我们自定义的元类而不是 type,我们可以给类注入一些行为(we can inject some behavior to the classes that wouldn't have been possible)。但是,在我们实现通过元类注入行为之前,让我们来看看Python中更常见的实现元编程的方法。

装饰器(Decorators):Python中元编程的一个常见示例

装饰器是一种修改函数行为或者类行为的方法。装饰器的使用看起来大概是这个样子:

@some_decorator
def some_func(*args, **kwargs):
    pass

@some_decorator只是一种语法糖,表示函数some_func被另一个函数some_decorator封装起来。我们知道函数和类(除了 type 这个元类)在Python中都是对象,这意味着它们可以:

  • 分配给一个变量(Assigned to a variable)
  • 复制(copied)
  • 作为参数传递给另一个函数(Passed as parameters to other functions)

上面的写法等同于

some_func = some_decorator(some_func)

你可能会想知道 some_decorator 是如何定义的

def some_decorator(f):
    """
    The decorator receives function as a parameter.
    """
    def wrapper(*args, **kwargs):
        # doing something before calling the function
        f(*args, **kwargs)
        # doing something after the function is called
    return wrapper

现在假设我们有一个从URL抓取数据的函数。被抓取服务器上有限流机制当它检测到同一个IP地址发来过多的请求并且请求间隔都一样时,会限制当前IP的请求。为了让我们的抓取程序表现的更随机一些,我们会让程序在每次请求之后暂定一小段随机时间来“欺骗”被抓取服务器。这个需求我们能通过装饰器来实现么?看代码

from functools import wraps
import random
import time

def wait_random(min_wait=1, max_wait=5):
    def inner_function(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            time.sleep(random.randint(min_wait, max_wait))
            return func(*args, **kwargs)
        return wrapper
    return inner_function

@wait_random(10, 15)
def function_to_scrape():
    # some scraping stuff

其中 inner_function 和 @wraps 装饰器可能对你来说还比较新。如果你仔细看,inner_function 和我们上文中定义的 some_decorator 类似。之所以用了三层def关键字,是因为装饰器wait_random要接受参数(min_wait和max_wait)。@wraps是个很好用的装饰器,他保存原函数(这里是func)的元数据(例如name, doc string, and function attributes)。如果我们没有用 @wraps,当我们对装饰之后的函数调用 help() 时 将不能得到有用的(期望的)结果,它将返回 wrapper 函数的 docstring,而不是 func 函数的(正常我们期望是func的)。

但是如果你有一个爬虫类包含多个类似的函数呢:

class Scraper:
    def func_to_scrape_1(self):
        # some scraping stuff
        pass
    def func_to_scrape_2(self):
        # some scraping stuff
        pass
    def func_to_scrape_3(self):
        # some scraping stuff
        pass

一种方案是对每个方法前都用 @wait_random 进行装饰。但是我们可以做的更优雅:我们可以创建一个“类装饰器”。思路是遍历类的名称空间,识别出函数,然后用我们的装饰器进行封装

def classwrapper(cls):
    for name, val in vars(cls).items():
        # `callable` return `True` if the argument is callable
        # i.e. implements the `__call`
        if callable(val):
            # instead of val, wrap it with our decorator.
            setattr(cls, name, wait_random()(val))
    return cls

现在我们可以用 @classwrapper 来封装整个Scraper类。但是再进一步,如果我们有很多和Scraper相似的类呢?当然你可以分别对每个类用 @classwrapper 进行装饰,但是也可以更优雅:创建一个元类。

元类(Metaclasses)

编写一个元类包含两步:

  1. 创建一个子类继承自元类 type(Write a subclass of the metaclass type)
  2. 通过“元类钩子”将新的元类插入到类创建过程(Insert the new metaclass into the class creation process using the metaclass hook)

我们创建 type 元类的子类,修改一些魔术方法,像__init____new____prepare__以及__call__以实现在创建类的过程中修改类的行为。这些方法包含了像父类,类名,属性等信息。Python2中,元类钩子(metaclass hook)是类中一个名为__metaclass__的静态属性(the metaclass hook is a static field in the class called metaclass)。Python3中, 你可以在类的基类列表中指定元类作为元类参数(you can specify the metaclass as a metaclass argument in the base-class list of a class)。

>>> class CustomMetaClass(type):
...     def __init__(cls, name, bases, attrs):  
...         for name, value in attrs.items():
                # do some stuff
...             print('{} :{}'.format(name, value))
>>> class SomeClass(metaclass=CustomMetaClass):
...     class_attribute = "Some string"

__module__ :__main__
__metaclass__ :<class '__main__.CustomMetaClass'>
class_attribute :Some string

属性被自动打印出来由于 CustomMetaClass 中的 __init__方法。我们来假设一下在你的Python项目中有一位“烦人”的伙伴习惯用 camelCase(驼峰法)方式来命名类中的属性和方法。你知道这不是一条好的实践,应该用 snake_case(即下划线方式)方式。那么我们可以编写一个元类来讲所有驼峰法的属性名称和方法名称修改为下划线方式吗?

def camel_to_snake(name):
    """
    A function that converts camelCase to snake_case.
    Referred from: https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
    """
    import re
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

class SnakeCaseMetaclass(type):
    def __new__(snakecase_metaclass, future_class_name,
                future_class_parents, future_class_attr):
        snakecase_attrs = {}
        for name, val in future_class_attr.items():
            snakecase_attrs[camel_to_snake(name)] = val
        return type(future_class_name, future_class_parents,
                    snakecase_attrs)

你可能已经注意到这里用了__new__方法而不是__init__。实际上 __new是创建一个实例过程的第一步,它负责返回由类实例化而来的实例。另一方面, \init并不返回任何东西,它仅仅负责在实例创建之后对实例进行各种初始化。记住一个简单的法则:**当你需要控制一个实例的创建过程时用`new;当你需要对一个新创建的实例进行初始化时用init__`**。

一般在实现元类的时候不用 __init,因为他“不够强大”:在实际调用 \init之前类的创建过程已经完成。你可以理解`init`就像一个类装饰器,但不同的是 \init__在创建子类的时候会被调用,而装饰器则不会。

由于我们的任务包含创建一个新的实例(防止这些驼峰法的属性名称潜入到类中),重写我自定义元类 SnakeCaseMetaClass 中的 __new__方法。让我们来检查一下这是否按预期工作了:

>>> class SomeClass(metaclass=SnakeCaseMetaclass):
...     camelCaseVar = 5
>>> SomeClass.camelCaseVar
AttributeError: type object 'SomeClass' has no attribute 'camelCaseVar'
>>> SomeClass.camel_case_var
5

结果是预期的。现在你知道了Python中如何编写元类。

总结

在这篇文章中,介绍了Python中实例元类的关系。也展示了元编程的知识,这是一种操作代码的方法。我们还讨论了装饰器类装饰器用来对类和方法(函数)注入一些额外的行为。然后我们展示了如何通过继承默认的元类type来创建自定义的元类。最后我们展示了一些用到元类的场景。关于是否使用元类,在网上也有比较大的争议。但是通过本文我们应该能分析什么类型的问题用元编程来解决可能会更好。

由于本人能力有限,若有有不精准或模糊的地方,请见原文链接。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Python中的元编程

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python中的元编程

就像元数据是关于数据的数据一样,元编程是编写程序来操作程序(Just like metadata is data about data, metaprogramming is writing programs that manipulate
2023-01-31

Python中的元编程怎么应用

这篇“Python中的元编程怎么应用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python中的元编程怎么应用”文章吧。什
2023-07-05

OpenERP与Python 元编程

Python元编程被称为“黑魔法”。Python界的传奇人物Tim Peters有云: 引用 Python的元编程这种黑魔法99%的人都无需了解,如果你拿不准是否应该用到它时,你不需要它. OpenERP基本遵循了Tim Peters的教诲
2023-01-31

如何使用Python中的元编程技巧

如何使用Python中的元编程技巧导语:元编程是一种编程范式,指的是在运行时创建或修改代码的能力。Python作为一门动态语言,具备强大的元编程能力。本文将介绍Python中常用的元编程技巧,并给出具体的代码示例。一、使用元类元类是用于创建
2023-10-22

python元编程指的是什么

Python元编程是指在Python中编写能够操作、创建和修改Python代码的代码。它允许程序在运行时动态地创建和修改代码,以便实现自定义的行为和逻辑。元编程可以用于创建装饰器、类装饰器、元类、属性访问器、动态导入模块等。通过元编程,程序
2023-10-25

python元类编程的基本使用

本文主要介绍了python元类编程的基本使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-02-22

Python元编程:赋予你编程超能力的工具

Python元编程是一种强大的技术,它允许你对Python语言本身进行操作,赋予你编程超能力。
Python元编程:赋予你编程超能力的工具
2024-02-14

Python元编程:开启极客编程的颠覆之旅

Python 元编程打开了一扇通往极客编程的大门,允许开发人员操纵代码本身,创造出更具动态性和适应性的应用程序。本文将带领你深入探索元编程的奥秘,并通过实例演示如何利用其强大功能来提升 Python 的编程效率和灵活性。
Python元编程:开启极客编程的颠覆之旅
2024-02-14

Python元编程技术一览表

Python元编程技术是一系列可以修改或扩展Python语言本身的技术,它允许程序员在运行时修改类和函数的定义,从而实现代码的动态生成和修改。
Python元编程技术一览表
2024-02-14

python元类编程如何使用

本文小编为大家详细介绍“python元类编程如何使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“python元类编程如何使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1.1.propety动态属性在面向对
2023-07-05

Python编程Day6——元组类型、字

1、用途:记录多个值,当多个值没有改变的需求此时元组更为合适2、定义:在()内用逗号分隔开多个任意类型的值(参数为for可以循环的对象)3、常用操作:索引(正取向+反取向)且只能取值不能改变t=('egon',123,['a','b'])p
2023-01-31

掌握Python元编程,成就编码大师

: 本文介绍了Python元编程的强大威力,它能让我们操作Python的编译过程,以编程的方式去编程,从而实现许多不可思议的功能。
掌握Python元编程,成就编码大师
2024-02-14

换个角度理解python元编程

元编程这个概念本身不新,只是没有发现原来很早就在用这个东西,所以python等下再聊,先理一理怎么理解编程这个事情。我仔细思考,其实是在做一件设计想法,纪录想法,实现想法的过程。怎么样设计想法?应该需要一些图形,文字通过一定格式纪录下来,反
2023-01-31

使用Python怎么实现元编程

本篇文章为大家展示了使用Python怎么实现元编程,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、前言首先说,Python中一切皆对象,老生常谈。还有,Python提供了许多特殊方法、元类等等这样
2023-06-15

Python元类编程实现一个简单的ORM

本文主要介绍了Python元类编程实现一个简单的ORM,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-03-06

翻转Python魔术帽:理解元编程的哲学

Python是一个灵活的编程语言,在元编程提供对其本身进行操作的手段,如修改代码、动态生成类、生成函数和方法等等。它可以用来灵活构建代码、动态生成类等等。元编程让Python更具表现力,适应复杂环境。
翻转Python魔术帽:理解元编程的哲学
2024-02-14

超越Python边界:元编程的力量与优雅

元编程是Python中一项强大而优雅的技术,允许程序员在运行时动态地修改和扩展程序的行为。它提供了一种创建代码的代码的机制,从而可以生成定制和通用的应用程序。
超越Python边界:元编程的力量与优雅
2024-02-14

python 编程中的__doc__的使

今天给大家介绍个我们在编程中需要的一个小玩意,我们在运行程序的时候,经常需要是否这个脚本是否需要输入各种参数等等,才能正常运行等,今天就给大家介绍个全局的变量的使用       下面看下第一种写法: 1: [root@centos6
2023-01-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录