Python元编程

2015/04/13 Python

Python元编程

软件开发中最重要的一条真理就是“不要重复自己的工作(Don’t repeat yourself)”。也就是说,任何时候当需要创建高度重复的代码(或者需要复制粘贴源代码)时,通常都需要寻找一个更加优雅的解决方案。在Python中,这类问题常常会归类为“元编程”。

元编程

元编程的主要目标是创建函数和类,并用它们来操纵代码(比如说修改、生成或者包装已有的代码)。Python中基于这个目的的主要特性包括装饰器、类装饰器以及元类。但是,还有许多其他有用的主题——包括对象签名、用exec()来执行代码以及检查函数和类的内部结构——也进入了我们的视野。

什么是元编程

Python元编程是指在运行时对Python代码进行操作的技术,它可以动态地生成、修改和执行代码,从而实现一些高级的编程技巧。Python的元编程包括元类、装饰器、动态属性和动态导入等技术,这些技术都可以帮助我们更好地理解和掌握Python语言的特性和机制。元编程在一些场景下非常有用,比如实现ORM框架、实现特定领域的DSL、动态修改类的行为等。掌握好Python元编程技术可以提高我们的编程能力和代码质量。

想要搞定元编程,必须要理解和掌握Python中的元编程技术:

  • 反射:Python提供了许多内置函数和模块,如getattr()、setattr()、hasattr()、inspect等,可以在运行时动态地获取对象的属性和方法信息,从而实现反射。
  • 装饰器:装饰器是Python中一种常见的元编程技术,它可以动态地修改函数或类的行为,而无需修改它们的源代码。装饰器可以用于函数的参数检查、性能分析、缓存、日志记录等方面。
  • 类装饰器:类装饰器是一种对类进行修饰的装饰器,可以在类定义时动态地修改类的行为。类装饰器可以用于实现单例模式、代理模式、混入等方面。
  • 元类:元类是Python中一种高级的元编程技术,它可以动态地创建类,而不是实例。元类可以用于控制类的创建行为、添加类的属性和方法、实现ORM框架等方面。 在实际开发中,元编程可以用于实现一些高级的技术,如ORM框架、RPC框架、动态路由等。掌握Python的元编程技术,可以让开发者更好地理解Python的语言特性,提高代码的可读性和可维护性。

元编程应用场景

Python元编程的实际应用场景非常广泛,例如下面几个典型的场景:

  • 装饰器和元类 装饰器和元类是Python中常见的元编程技巧,通过这两种技术可以实现对类和函数进行动态的修改和扩展。比如,可以使用装饰器来增强函数的功能,也可以使用元类来动态生成类。
  • 动态生成代码 Python中的eval和exec函数可以用于动态地生成代码并执行,这是元编程的一种典型应用场景。比如,可以根据用户的输入动态地生成SQL语句或其他代码。
  • 插件化架构 在插件化架构中,程序可以在运行时动态地加载和卸载插件。Python中的模块和包机制可以用于实现插件化架构,而元编程技巧则可以用于实现动态的插件加载和卸载。
  • 协程和异步编程 在协程和异步编程中,需要对代码进行动态的修改和重构,以便实现高效的并发处理。Python中的asyncio和curio等库都是基于元编程技巧实现的。
  • 基于属性的编程 Python中的属性可以用于动态地访问对象的属性,这是元编程的一种典型应用场景。比如,可以使用属性来实现动态的类型转换、数据校验和计算属性等功能。 Python元编程的应用场景非常广泛,可以用于实现各种动态的、高级的编程功能。

装饰器

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

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 的时候执行。

##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

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

  • 如果希望某些函数拥有相同的功能,希望不改变原有的调用方式、不写重复代码、易维护,可以使用装饰器来实现。
  • 如果希望某一些类拥有某些相同的特性,或者在类定义实现对其的控制,我们可以自定义一个元类,然后让它类的元类指向该类。
  • 如果希望实例的属性拥有某些共同的特点,就可以自定义一个描述符类。

综合实战

使用元类来实现一个简单的ORM框架

class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return super().__new__(cls, name, bases, attrs)
 
        table_name = attrs.get('table_name', name.lower())
        mappings = {}
        fields = []
 
        for k, v in attrs.items():
            if isinstance(v, Field):
                mappings[k] = v
                fields.append(k)
 
        for k in mappings.keys():
            attrs.pop(k)
 
        attrs['__table__'] = table_name
        attrs['__mappings__'] = mappings
        attrs['__fields__'] = fields
 
        return super().__new__(cls, name, bases, attrs)
 
 
class Model(metaclass=ModelMetaClass):
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)
 
    def save(self):
        fields = []
        values = []
 
        for k, v in self.__mappings__.items():
            fields.append(v.db_column or k)
            values.append(getattr(self, k, None))
 
        sql = 'INSERT INTO {} ({}) VALUES ({})'.format(
            self.__table__,
            ', '.join(fields),
            ', '.join(['%s'] * len(values))
        )
 
        print('SQL:', sql)
        print('VALUES:', values)
 
 
class Field:
    def __init__(self, db_column=None):
        self.db_column = db_column
 
 
class StringField(Field):
    def __init__(self, db_column=None):
        super().__init__(db_column)
 
 
class IntegerField(Field):
    def __init__(self, db_column=None):
        super().__init__(db_column)
 
 
class User(Model):
    name = StringField(db_column='user_name')
    age = IntegerField(db_column='user_age')
    email = StringField(db_column='user_email')
 
 
if __name__ == '__main__':
    user = User(name='Tantianran', age=31, email='ttr@bbgops.com')
    user.save()

在上述代码中,使用元类ModelMetaClass动态地创建类,并根据类属性定义生成相应的数据库表结构和SQL语句。具体地,元类会通过类属性__mappings__、__fields__和__table__来生成相应的ORM映射关系和SQL语句。使用这种方式,我们可以在不写重复代码的情况下,轻松地创建一个简单的ORM框架,并实现对象到关系数据库的映射。

使用元类实现单例模式

class Singleton(type):
    _instances = {}
 
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]
 
class MyClass(metaclass=Singleton):
    pass

在这个示例中,我们定义了一个元类 Singleton,它维护了一个 _instances 字典来保存已经创建的实例。在元类的 call 方法中,我们检查当前类是否已经存在于 _instances 字典中,如果不存在,就使用 super().call 方法创建一个新的实例,并将其保存到 _instances 字典中,最后返回该实例。这样,无论我们创建多少个 MyClass 类的实例,都只会得到同一个实例。

使用元类实现装饰器

class my_decorator(object):
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print("Before the function is called.")
        self.func(*args, **kwargs)
        print("After the function is called.")
 
class Myclass(object):
    @my_decorator
    def my_method(self):
        print("Hello world.")
 
obj = Myclass()
obj.my_method()

在这个示例中,我们定义了一个装饰器类 my_decorator,它接受一个函数作为参数,并在函数调用前后输出一些信息。在类 Myclass 的 my_method 方法上使用 @my_decorator 装饰器,就相当于将 my_method 方法替换为一个新的方法,该新方法会在原来的方法前后输出信息。

使用元类实现方法缓存

class memoize(object):
    def __init__(self, func):
        self.func = func
        self.cache = {}
    def __call__(self, *args):
        if args in self.cache:
            return self.cache[args]
        else:
            value = self.func(*args)
            self.cache[args] = value
            return value
 
@memoize
def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)

在这个示例中,我们定义了一个装饰器类 memoize,它接受一个函数作为参数,并使用一个字典来保存函数的输入和输出。在 call 方法中,我们首先检查函数的输入是否已经在字典中,如果是,则直接返回字典中对应的输出;否则,就调用原来的函数计算输出,并将输入和输出保存到字典中,最后返回输出。这样,如果我们多次调用带有 @memoize 装饰器的函数,对于相同的输入,就只会计算一次,从而大大提高了性能。

使用元编程技术动态生成代码

class DynamicClass(type):
    def __new__(mcs, name, bases, attrs):
        # 添加属性
        attrs['author'] = 'John Doe'
 
        # 添加方法
        def hello(self):
            return f'Hello, I am {self.name}'
 
        attrs['hello'] = hello
 
        return super().__new__(mcs, name, bases, attrs)
 
# 使用元类创建类
MyClass = DynamicClass('MyClass', (), {'name': 'Alice'})
 
# 访问属性和方法
print(MyClass.name) # 输出:Alice
print(MyClass.author) # 输出:John Doe
obj = MyClass()
print(obj.hello()) # 输出:Hello, I am Alice

在上面的示例中,使用了元类DynamicClass来动态创建类,__new__方法在类创建时被调用,用来动态添加属性和方法。在这个例子中,我们通过__new__方法向MyClass类中添加了一个author属性和一个hello方法。最后创建了MyClass类的一个实例,并调用了它的hello方法。

Search

    微信好友

    博士的沙漏

    Table of Contents