Python 元编程

Posted 堆码志

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,可能会用到;
作者:袁首京

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

Python中的元编程

元编程

什么是元编程?

用代码来生成代码,或者说,用程序来生成程序,就叫元编程。Python能通过反射实现元编程。

什么是元类?

与元编程相关的一个概念是元类。什么是元类呢,具体到Python,用来创建类的类,就叫元类。元类是制造类的工厂。

在Python中,一个普通类创建出来的东西是类的实例,实例是一个对象。而元类也是一种类,它创建出来的东西是另一个普通类,普通类也是一个对象(Python中一切皆对象),然后这个普通类,又可以创建出类的实例。所以说,元类是类的类。它们的构建过程如下图:

Python中:

1、所有非object类都继承自object类(包括type)
2、所有类的类型都是type(包括type、object、元类)
3、type类继承自object(符合第1句)

虽然type也继承自object,但是我们自己写的类,继承自object与继承自type,会有些不同。

type元类

type这个类,不仅可以获取Python中对象的类型,比如type(123),会告诉你123是int类型(int也是类)。

type也可以作为元类,来构建其他类。语法是:

type(name, bases, dict) -> a new type 返回一个新的类型

其中,name为类的名称,bases为类的继承列表,dict为类的属性字典。比如:

def __init__(self):
    self.x = 123

# 用type创建了一个新的类,变量名NewClass,新类的类名是newclass
NewClass = type('newclass', (object,), 'a':100, 'b':[], '__init__':__init__)
print(NewClass.__dict__)  # 类的属性里会有a, b, __init__

# 等价于如下class关键字创建的类
class newclass(object):
    a = 100
    b = []
    def __init__(self):
        self.x = 456
print(newclass.__dict__)

# 不同之处在于,type创建的newclass的__init__函数在全局作用域里,
# class关键字创建的newclass的__init__函数在类内部,即newclass.__init__

以上例子没有什么神奇。元类的真正用途,是在元类的构造方法(__new__, __init__)里写我们的代码,从而改变一个类的构建行为。

上面用type构建了一个新的类,类在程序中是代码,type(…)调用也是代码。所以,这种用代码来生成代码的过程,就是元编程

构建自己的元类

如果一个类继承自type(而不是object),它就会成为元类,比如:

# 创建元类,ModelMeta是元类,因为继承自type
# 我们把ModelMeta当作元类模版
class ModelMeta(type):
    
    # 因为继承自type,参数与type元类一样
    # 在__new__中可以改变构建普通类时的行为
    def __new__(cls, name, bases, _dict):
        print(cls)
        print(name)
        print(bases)
        print(_dict)
        
        # 注意这里和普通类调用super不同: return super().__new__(cls)
        return super().__new__(cls, name, bases, _dict)
        # 或者
        #return type.__new__(cls, name, bases, _dict)

# 1、用metaclass关键字参数创建一个元类
# 这里的写法表示A这个类是被元类ModelMeta构建的,而不是继承
# 这种写法是用元类构建一个类A,所以元类里的__new__实例化方法会执行
# 即元类实例化出来的东西是另一个普通类(而不是一个类的实例)
# A是普通类
class A(metaclass=ModelMeta):
    x = 999
    def __init__(self):
        print('A.init')

print('=' * 30)
# 2、继承
# 这里才表示正常的类的继承,
# 但是B的构建路线是:ModelMeta->构建A->继承到B
# B是普通类
class B(A):
    def __init__(self):
        print('B.init')
        
print('=' * 30)
# 3、元类模版也可以使用调用语法,创建新类,就像type那样
# C是普通类
C = ModelMeta('C', (), 'x':999)

print('=' * 30)
# 4、继承元类
# D继承自元类ModelMeta,所以D也是一个元类(这里是继承而不是用元类构建D)
# 因为是继承,元类ModelMeta的__new__不会执行
class D(ModelMeta):
    pass

元类的应用

元编程可用于开发框架。比如ORM(对象关系映射)。将数据库的操作与类(以及实例)的操作联系起来,就可以使用元类。

映射关系:

表 ⟶ class
行 ⟶ 实例
字段 ⟶ 属性(描述器,见 这里

一个简单的例子:

# 字段类
class Field:
    # 字段名fn,类型tp,是否为主键pk,是否空null
    def __init__(self, fn=None, tp=None, pk=False, null=True):
        self.fn = fn
        self.tp = tp
        self.pk = pk
        self.nl = null
        self.value = None
    
    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        if self.tp:
            self.value = self.tp(value)
    
    def __delete__(self, instance):
        del self.value


# 模版元类
class ModelMeta(type):
    def __new__(cls, name, bases, attrs):
        #print(cls)
        #print(name)
        #print(bases)
        #print(attrs)
        
        # 添加表名
        if '__tblname__' not in attrs.keys():
            attrs['__tblname__'] = name
            
        primarykeys = []
        for k,v in attrs.items():
            if isinstance(v, Field):
                if v.fn is None:
                    v.fn = k  # 属性名作为字段名
                if v.pk:
                    primarykeys.append(v)
                    v.nl = False  # 有主键自动不为空
                    
        attrs['__primarykeys__'] = primarykeys

        return type.__new__(cls, name, bases, attrs)


# 基类
class Base(metaclass=ModelMeta):
    def __init__(self, **kwargs):
        for k,v in kwargs.items():
            if k not in self.__class__.__dict__:
                raise AttributeError(f"'k' is not a field name")
            setattr(self, k, v)  # 将调用描述器的__set__
            

# 设计学生表
class Student(Base):
    sid = Field(tp=int, pk=True)  # 描述器
    name = Field('username', tp=str, null=False)
    age = Field(tp=int)
    
    def __repr__(self):
        return f"<self.__class__.__name__ sid=self.sid name=self.name age=self.age>"


# s = Student(sid=1, gender=112)
s = Student(sid=1)
print(s)

print('=' * 30)
s.name = 123
s.age=20
print(s)

print('=' * 30)
print(s.name, s.age, type(s.name))

以上是关于Python 元编程的主要内容,如果未能解决你的问题,请参考以下文章

Python元编程元类

Python中的元编程

Python中的元编程

Python中的元编程

Python中的元编程

Python 元编程