Python 元编程

作者:袁首京

原创文章,转载时请保留此声明,并给出原文连接。

元编程并不象它听起来那么时髦和新奇。常用的 decorator 就可以认为是一种元编程。简单来说,元编程就是编写操作代码的代码。

有点绕,是吧?别着急,咱们一点一点来讨论。

注意:本文中的代码适用于 Python 3.3 及以上。

元类

多数编程语言中,一切东西都有类型。Python 也不例外,我们可以用 type() 函数获取任意变量的类型。

num = 23
print("Type of num is:", type(num))

lst = [1, 2, 4]
print("Type of lst is:", type(lst))

name = "Atul"
print("Type of name is:", type(name))

执行结果是:

Type of num is: <class 'int'>
Type of lst is: <class 'list'>
Type of name is: <class 'str'>

Python 中的所有类型都是由 Class 定义的。这一条与其它编程语言,比如 Java、C++ 等等不同。在那些语言中,int、char、float 之类是基本数据类型,但是在 Python 中,它们是 int 类或 str 类的对象。

象其它 OOP 语言一样,我们可以使用 class 定义新类型:

class Student:
    pass

stu_obj = Student()
print("Type of stu_obj is:", type(stu_obj))

执行结果是:

Type of stu_obj is: <class '**main**.Student'>

一点儿也不意外,对吧?其实有意外,因为在 Python 中,类也是一个对象,就像任何其他对象一样,它是元类的实例。即一个特殊的类,创建了 Class 这个特殊的类实例。看如下代码:

class Student:
    pass

print("Type of Student class is:", type(Student))

执行结果是:

Type of Student class is: <class 'type'>

既然类也是一个对象,所以以修改对象相同的方式修改它就顺理成章。如下先定义一个没有任何属性和方法的类,然后在外部为其添加属性和方法:

class test:
    pass

test.x = 45
test.foo = lambda self: print('Hello')

myobj = test()
print(myobj.x)
myobj.foo()

执行结果是:

45
Hello

以上过程可以简单概括为:

元类创建类,类创建实例

画个图象这样:

元类 -> 类 -> 实例

因此,我们就可以编写自定义的元类,执行额外的操作或者注入代码,来改变类的生成过程。这在某些场景下很有用,主要是比如有些情况下使用元编程更简单,另一些情况只有元编程才能解决问题。

创建自定义元类

创建自定义元类,有两种方法。第一种是继承 type 元类,并且覆写两个方法:

  1. new()

它在 init() 之前调用,生成类实例并返回。我们可以覆盖此方法来控制对象的创建过程。

  1. init()

这个不多解释,相信你都明白。

如下是个例子:

class MultiBases(type):
    def __new__(cls, clsname, bases, clsdict):
        if len(bases)>1:
            raise TypeError("Inherited multiple base classes!!!")

        return super().__new__(cls, clsname, bases, clsdict)


class Base(metaclass=MultiBases):
    pass


class A(Base):
    pass


class B(Base):
    pass


class C(A, B):
    pass

执行结果是:

Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 8, in **new**
TypeError: Inherited multiple base classes!!!

第二种方法是直接使用 type() 函数创建类。这个方法如果只用一个参数调用,它会返回该参数的类型,前文已经描述过。但是使用三个参数调用时,它会创建一个类。这三个参数如下:

  1. 类名称;
  2. 继承的父类的元组。你没看错,是元组,别忘了 Python 可以多继承;
  3. 一个字典。定义类属性和方法;

以下是示例:

def test_method(self):
    print("This is Test class method!")


class Base:

    def myfun(self):
        print("This is inherited method!")


Test = type('Test', (Base, ), dict(x="atul", my_method=test_method))
print("Type of Test class: ", type(Test))

test_obj = Test()
print("Type of test_obj: ", type(test_obj))

test_obj.myfun()
test_obj.my_method()

print(test_obj.x)

执行结果是:

Type of Test class: <class 'type'>
Type of test_obj: <class '**main**.Test'>
This is inherited method!
This is Test class method!
atul

使用元类解决问题

了解了元类的创建方法后,可以来解决一些实际问题了。例如,如果我们想在每次调用类方法时,都先输出一下它的全限定名,该怎么办呢?

最常用的方法是使用 decorator,象这样:

from functools import wraps


def debug(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Full name of this method:", func.__qualname__)
        return func(*args, **kwargs)
    return wrapper


def debugmethods(cls):
    for key, val in vars(cls).items():
        if callable(val):
            setattr(cls, key, debug(val))
    return cls


@debugmethods
class Calc:

    def add(self, x, y):
        return x+y

    def mul(self, x, y):
        return x\*y

    def div(self, x, y):
        return x/y


mycal = Calc()
print(mycal.add(2, 3))
print(mycal.mul(5, 2))

执行结果是:

Full name of this method: Calc.add
5
Full name of this method: Calc.mul
10

这个方案很漂亮。但是,如果变更一下需求,例如我们希望 Calc 的所有子类的方法执行时,都先输出一下它的全限定名,该怎么办呢?

在每一个子类上加上 @debugmethods 是一种方案,但是有点啰嗦,是不是?

该基于元类的解决方案出场了,以下是个例子:

from functools import wraps


def debug(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Full name of this method:", func.__qualname__)
        return func(*args, **kwargs)
    return wrapper


def debugmethods(cls):

    for key, val in vars(cls).items():
        if callable(val):
            setattr(cls, key, debug(val))
    return cls


class debugMeta(type):

    def __new__(cls, clsname, bases, clsdict):
        obj = super().__new__(cls, clsname, bases, clsdict)
        obj = debugmethods(obj)
        return obj


class Base(metaclass=debugMeta):
    pass


class Calc(Base):

    def add(self, x, y):
        return x+y


class Calc_adv(Calc):

    def mul(self, x, y):
        return x\*y


mycal = Calc_adv()
print(mycal.mul(2, 3))

执行结果是:

Full name of this method: Calc_adv.mul
6

何时使用元类

该说的基本说完了,剩下最好一件事。元编程算是 Python 的一个魔法,多数时候我们其实用不到。但是什么时候需要呢?大概有三种情况:

  • 如果我们想要一个特性,沿着继承层次结构向下传递,可以用;
  • 如果我们想在类创建后,能动态修改,可以用;
  • 如果我们是在开发类库或者 API,可能会用到;
作者:袁首京

原创文章,转载时请保留此声明,并给出原文连接。

原文链接:https://www.cnblogs.com/rockety/p/17298553.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python 元编程 - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • python如何实现内容写在图片上

    想要在图片上添加文字内容,需要使用Python中的Pillow库。Pillow是Python的一个图像处理库,可以使用它加载、处理和保存多种格式的图像文件,包括bmp、gif、jpg、png以及bmp等格式。 具体操作流程如下: 安装Pillow库 首先需要确保安装了pip,然后执行下列命令即可完成Pillow的安装: pip install Pillow …

    python 2023年5月18日
    00
  • Python 字符串使用多个分隔符分割成列表的2种方法

    使用多个分隔符将字符串分割成列表通常是在数据处理和解析文本时非常有用的一种技巧。Python 提供了多种方法实现该功能,本文将介绍两种常用的方法。 方法一:使用 re 模块 Python re 模块提供了丰富的正则表达式支持,可以用来处理字符串的复杂匹配和替换。使用 re.split() 方法可以方便地将字符串按照多个不同的分隔符分割成列表。 import …

    python 2023年5月14日
    00
  • python 实现的车牌识别项目

    Python 实现的车牌识别项目攻略 1. 车牌识别项目简介 车牌识别项目是一个利用计算机视觉技术实现的智能交通系统,通过摄像头获取车辆的图片,对车牌进行识别,从而实现自动化管理。本项目使用Python语言进行开发,采用了OpenCV和Keras等常用的计算机视觉和机器学习库。 2. 项目开发流程 2.1 数据采集 首先需要采集大量的车牌图片进行训练,可以使…

    python 2023年5月18日
    00
  • python re.sub()替换正则的匹配内容方法

    以下是详细讲解“Python re.sub()替换正则的匹配内容方法”的完整攻略,包括re.sub()函数的基本语法、使用re.sub()函数替换匹配内容的方法和两个示例说明。 re.sub()函数的基本语法 re.sub()函数用于在字符串中替换正则表达式的匹配项。re.sub()函数的基本语法如下: re.sub(pattern, repl, strin…

    python 2023年5月14日
    00
  • Python GUI之如何使用tkinter控件

    Python GUI 是面向图形用户界面的编程,其实现的方式有多种,其中较为常见的有使用 tkinter 库开发,tkinter 是 Python 自带的 GUI 工具包,常用于快速开发各种桌面应用和窗口程序。以下是使用 tkinker 控件的完整攻略: 安装 tkinter 由于 tkinter 是 Python 自带的库,所以只需确认 Python 版本…

    python 2023年6月6日
    00
  • python pyinstaller库

    简要 pyinstaller模块主要用于python代码打包成exe程序直接使用,这样在其它电脑上即使没有python环境也是可以运行的。 用法 一.安装 pyinstaller属于第三方库,因此在使用的时候需提前安装 pip install pyinstaller 二.配置spec文件 1.配置生成exe程序文件夹 (1)如果不熟悉spec配置内容,可以在…

    python 2023年4月25日
    00
  • python csv一些基本操作总结

    Python CSV一些基本操作总结 CSV(Comma-Separated Values)是一种常见的文件格式,用于存储表格数据。它可以被几乎所有的电子表格和数据库程序导入和导出。 Python内置的csv模块可以方便地读取、写入CSV文件,下面我们来详细讲解一下Python CSV模块的一些基本操作。 读取CSV文件 我们可以使用csv模块中的reade…

    python 2023年6月3日
    00
  • python字典中get()函数的基本用法实例

    下面我将为您详细讲解 Python 字典中 get() 函数的基本用法和实例。 什么是 Python 字典? 在了解 get() 函数前,我们先来了解一下 Python 字典。Python 字典是一种无序的、可变的、映射类型的数据结构,通常用于存储键值对. Python 字典由花括号 {}、表达式组成,表达式中每个元素都是一个键值对,键和值之间用冒号 : 分…

    python 2023年5月13日
    00
合作推广
合作推广
分享本页
返回顶部