您现在的位置是:亿华云 > 人工智能
说说Python的元编程
亿华云2025-10-09 03:24:46【人工智能】6人已围观
简介提到元这个字,你也许会想到元数据,元数据就是描述数据本身的数据,元类就是类的类,相应的元编程就是描述代码本身的代码,元编程就是关于创建操作源代码(比如修改、生成或包装原来的代码)的函数和类。主要技术是
提到元这个字,说说你也许会想到元数据,编程元数据就是说说描述数据本身的数据,元类就是编程类的类,相应的说说元编程就是描述代码本身的代码,元编程就是编程关于创建操作源代码(比如修改、生成或包装原来的说说代码)的函数和类。主要技术是编程使用装饰器、元类、说说描述符类。编程本文的说说主要目的是向大家介绍这些元编程技术,并且给出实例来演示它们是编程怎样定制化源代码的行为。
装饰器
装饰器就是说说函数的函数,它接受一个函数作为参数并返回一个新的编程函数,在不改变原来函数代码的网站模板说说情况下为其增加新的功能,比如最常用的计时装饰器:
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 的时候执行。
如果想更好的理解元类,可以阅读Python黑魔法之metaclass
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(fExpected { 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(fExpected { value!r} to be an int or float) if self.minvalue is not None and value < self.minvalue: raise ValueError( fExpected { value!r} to be at least { self.minvalue!r} ) if self.maxvalue is not None and value > self.maxvalue: raise ValueError( fExpected { 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(fExpected { value!r} to be an str) if self.minsize is not None and len(value) < self.minsize: raise ValueError( fExpected { value!r} to be no smaller than { self.minsize!r} ) if self.maxsize is not None and len(value) > self.maxsize: raise ValueError( fExpected { value!r} to be no bigger than { self.maxsize!r} ) if self.predicate is not None and not self.predicate(value): raise ValueError( fExpected { 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描述器阻止无效实例的创建:
>>> Component(Widget, metal, 5) # Blocked: Widget is not all uppercase Traceback (most recent call last): ... ValueError: Expected <method isupper of str objects> to be true for Widget >>> Component(WIDGET, metle, 5) # Blocked: metle is misspelled Traceback (most recent call last): ... ValueError: Expected metle to be one of { metal, plastic, wood} >>> Component(WIDGET, metal, -5) # Blocked: -5 is negative Traceback (most recent call last): ... ValueError: Expected -5 to be at least 0 >>> Component(WIDGET, metal, V) # Blocked: V isnt a number Traceback (most recent call last): ... TypeError: Expected V to be an int or float >>> c = Component(WIDGET, metal, 5) # Allowed: The inputs are valid最后的话
关于 Python 的元编程,总结如下:
如果希望某些函数拥有相同的功能,希望不改变原有的调用方式、不写重复代码、易维护,可以使用装饰器来实现。
如果希望某一些类拥有某些相同的特性,或者在类定义实现对其的控制,我们可以自定义一个元类,然后让它类的元类指向该类。
如果希望实例的属性拥有某些共同的特点,就可以自定义一个描述符类。
很赞哦!(1935)
相关文章
- 4、待所有域名查询结束后可在右侧点击导出结果,即可以excel的文件方式将查询到的结果导出。
- 匿名函数自执行和闭包是一回事儿吗?
- 新 Red Hat 编译器工具箱:Clang、LLVM、GCC 等
- 为什么用Dubbo而不是Spring Cloud?基于支付场景的微服务高可用架构实战
- 前面这两个步骤都是在本机完成的。到这里还没有涉及真正的域名解析服务器,如果在本机中仍然无法完成域名的解析,就会真正请求域名服务器来解析这个域名了。
- 盘点那些曾经让程序员目瞪口呆的Bug都有什么?
- JS退位,Go语言是适合DevOps时代的编程语言
- 制作QQ微信支付宝三合一收款码
- 审核通过的域名将显示在域名竞拍页面,并进入正式拍卖期,买家可以在拍卖周期内出价,加价幅度与拍卖保证金说明,点此查看。
- Android UI及API优化指南