Python 元类编程

本文深入探讨Python中的元编程技术,包括如何在运行时创建类和定制类。通过类工厂函数展示了如何动态创建类,并利用元类实现类装饰器,以修改类的行为。同时,讲解了类装饰器的工作原理及其在类继承中的影响。此外,还讨论了元类在创建类时的作用,以及类属性和类方法。最后,通过一个具体的实体类例子,展示了如何使用元类来定制描述符并保持字段顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类元编程

元类编程指在运行时创建或定制类。

  • 在运行时创建类

类工厂函数

类是一等对象,可以使用函数新建类,而无须使用class关键字

def record_factory(cls_name, field_names):
    try:
        field_names = field_names.replace(',', ' ').split()
    except AttributeError:
        psss
    field_names = tuple(field_names)

    def __init__(self, *args, **kwargs):
        attrs = dict(zip(self.__slots__, args))
        attrs.update(kwargs)
        for name, value in attrs.items():
            setattr(self, name, value)

    def __iter__(self):
        """类的实例变成可迭代对象"""
        for name in self.__slots__:
            yield getattr(self, name)

    def __repr__(self):
        values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self))
        return '{}({})'.format(self.__class__.__name__ , values)

    cls_attrs = dict(__slots__ = field_names,
                     __init = __init__,
                     __iter__ = __iter__,
                     __repr__ = __repr__)

    return type(cls_name, (object,), cls_attrs) # type传入三个参数可以新建一个类,第二个参数是继承的类,第三个参数是dict类型的属性名和值

Dog = record_factory('Dog', 'name weight owner')
rex = Dog('Rex', 30, 'Bob')
print(rex)
name, weight, _ = rex # 解包
name, weight
"{2}'s dog weighs {1}kg".format(*rex)
rex.weight = 32
print(rex)
Dog.__mro__ # 继承的类

以上创建的类,名称是传入的cls_name,唯一的超类object,有__slots____init____iter____repr__四个类属性,三个实例方法。

定制描述符的类装饰器

类装饰器,参数为类对象的函数,返回原来的类或修改后的类

import abc


class AutoStorage:
    __counter = 0
    
    def __init__(self):
        cls = self.__class__
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix, index)
        cls.__counter += 1
        
    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return getattr(instance, self.storage_name)
        
    def __set__(self, instance, value):
        setattr(instance, self.storage_name, value)
        
        
class Validated(abc.ABC, AutoStorage):
    """抽象类,处理验证"""
    
    def __set__(self, instance, value):
        value = self.validate(instance, value)
        super().__set__(instance, value)
        
    @abc.abstractmethod
    def validate(self, instance, value):
        """raise"""
        
        
class Quantity(Validated):
    """处理数值"""
    
    def validate(self, instance, value):
        if value <= 0:
            raise ValueError('value must be > 0')
        else:
            return value
        

class NonBlank(Validated):
    """处理description为空"""
    def validate(self, instance, value):
        value = value.strip()
        if len(value) == 0:
            raise ValueError('value cannot be empty or blank')
        return value

            
    
def entity(cls):
    """类装饰器,修改各个描述符实例的storage_name属性"""
    for key, attr in cls.__dict__.items():
        if isinstance(attr, Validated):
            type_name = type(attr).__name__
            attr.storage_name = '_{}#{}'.format(type_name, key)
    return cls

            
@entity
class LineItem:
    description = NonBlank()
    weight = Quantity()
    price = Quantity()
    
    def __init__(self, description, weight, price):
        self.description = description
        self.weight = weight
        self.price = price
        
    def subtotal(self):
        return self.weight * self.price
    
raisins = LineItem('Golden raisins', 10, 6.95)
dir(raisins)[:3]
LineItem.description.storage_name
raisins.description
getattr(raisins, '_NonBlank#description')

类装饰器对被装饰的子类可能继承也可能不继承装饰器所做的改动。

导入时和运行时比较

解释器编译函数的定义体,把函数对象绑定到对应的全局名称上,但是不会执行函数的定义体。

对类,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。执行定义体时,定义了类的属性和方法,并构建了类对象。

调用在何时执行

# evalsupport.py
print('<[100]> evalsupport module start')

def deco_alpha(cls):
    print('<[200]> deco_alpha')
    
    def inner_1(self):
        print('<[300]> deco_alpha:inner_1')
        
    cls.method_y = inner_1
    return cls

class MetaAleph(type):
    print('<[400]> MetaAleph body')
    
    def __init__(cls, name, base, dic):
        print('<[500]> MetaAleph.__init__:inner_2')
        
        def inner_2(self):
            print('<[600]> MetaAleph.__init__:inner_2')
        
        cls.method_z = inner_2
    
print('<[700]> evalsupport module end')
# evaltime.py
from evalsupport import deco_alpha

print('<[1]> evaltime module start')

class ClassOne():
    print('<[2]> ClassOne body')
    
    def __init__(self):
        print('<[3]> ClassOne.__init__')
        
    def __del__(self):
        print('<[4]> ClassOne.__del__')
        
    def method_x(self):
        print('<[5]> ClassOne.method_x')
        
    class ClassTwo(object):
        print('<[6]> ClassTwo body')
        
        
@deco_alpha
class ClassThree():
    print('<[7]> ClassOne.method_x')
    
    def method_y(self):
        print('<[8]> ClassThree.method_y')
        

class ClassFour(ClassThree):
    print('<[9]> ClassFour body')
    
    def method_y(self): # 装饰器不会对此方法起作用
        print('<[10]> ClassFour.method_y')
        
        
if __name__ == '__main__':
    print('<[11]> ClassOne tests', 30 * '.')
    one = ClassOne()
    one.method_x()
    print('<[12]> ClassThree tests', 30 * '.')
    three = ClassThree()
    three.method_y()
    print('<[13]> ClassFour tests', 30 * '.')
    four = ClassFour()
    four.method_y()
    
print('<[14]> evaltime module end')

  • 在Python控制台中导入evaltime模块
>> import evaltime

100 # evalsupport 开始
400 # 类的定义体会运行
700 # evalsupport 结束

1 #evaltime 开始
2 # ClassOne 定义体
6 # ClassTwo 嵌套类定义体也会执行
7 # ClassThree 定义体
200 # 先计算被装饰的类的定义体,然后运行装饰器函数
9 # ClassFour 定义体
14 evaltime 结束
  • 在命令行中运行evaltime.py模块
> python evaltime.py

100 # evalsupport 开始
400 # MetaAleph 定义体
700 # evalsupport 结束

1 #evaltime 开始
2 # ClassOne 定义体
6 # ClassTwo 嵌套类定义体也会执行
7 # ClassThree 定义体
200 # 先计算被装饰的类的定义体,然后运行装饰器函数
9 # ClassFour 定义体

# 以上和import 导入相同

11 # 
3 # 运行main中的,ClassOne.__init__执行
5 # ClassOne.method_x方法运行

12 # 运行带类装饰器的
300 # ClassThree.method_y方法被装饰器修改了,运行deco_alpha的inner_1函数的定义体

13 # 运行继承被装饰的类ClassThree
10 # 继承的子类的ClassFour.method_y不被装饰器起作用,运行ClassFour.method_y定义体
14 # evaltime 结束
4 # 程序结束,运行ClassOne.__del__ 垃圾回收

元类

元类是制造类的工厂

默认情况下,Python中的类是type类的实例。type是大多数内置类和用户自定义类的元类。

'spam'.__class__
str.__class__
LineItem.__class__
type.__class__ # type是自身的实例
  • 元类从type类继承了构造类的能力

所有类都是type的实例,但是元类还是type的子类。

import collections
collections.Iterable.__class__
import abc
abc.ABCMeta.__class__
abc.ABCMeta.__mro__

元类执行顺序

from evalsupport import deco_alpha
from evalsupport import MetaAleph

print('<[1]> evaltime_meta module start')

@deco_alpha
class ClassThree():
    print('<[2]> ClassThree body')
    
    def method_y(self):
        print('<[3]> ClassThree.method_y')
        
        
class ClassFour(ClassThree):
    print('<[4]> ClassFour body')
    
    def method_y(self):
        print('<[5]> ClassFour.method_y')
        
class ClassFive(metaclass=MetaAleph):
    print('<[6]> ClassFive body')
    
    def __init__(self):
        print('<[7]> ClassFive.__init__')
        
    def method_z(self):
        print('<[8]> ClassFive.method_z')
        
class ClassSix(ClassFive):
    print('<[9]> ClassSix body')
    
    def method_z(self):
        print('<[10]> ClassSix.method_z')
        
if __name__ == '__main__':
    print('<[11]> ClassThree tests', 30 * '.')
    three = ClassThree()
    three.method_y()
    print('<[12]> ClassFour tests', 30 * '.')
    four = ClassFour()
    four.method_y()
    print('<[13]> Classfive tests', 30 * '.')
    five = ClassFive()
    five.method_z()
    print('<[14]> ClassSix tests', 30 * '.')
    six = ClassSix()
    six.method()
    
print('<[15]> evaltime_meta module end')
  • 在控制台导入evaltime_meta模块
>>> import evaltime_meta
<[100]> evalsupport module start # evalsupport 开始
<[400]> MetaAleph body # MetaAleph 定义体
<[700]> evalsupport module end # evalsupport 结束
<[1]> evaltime_meta module start # evaltime_meta 开始
<[2]> ClassThree body # ClassThree 定义体
<[200]> deco_alpha # 执行类装饰器
<[4]> ClassFour body # ClassFour 定义体

<[6]> ClassFive body # ClassFive 定义体 继承MetaAleph类
<[500]> MetaAleph.__init__:inner_2 # method_z方法被装饰器修改为inner_2
<[9]> ClassSix body # ClassSix 定义体 继承ClassFive类
<[500]> MetaAleph.__init__:inner_2 # method_z方法被装饰器修改为inner_2
<[15]> evaltime_meta module end # evaltime_meta 结束
  • 在命令行运行evaltime_meta.py模块
D:\PythonProject\Learning_python\fluent_python\code>python evaltime_meta.py
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body
<[200]> deco_alpha
<[4]> ClassFour body
<[6]> ClassFive body
<[500]> MetaAleph.__init__:inner_2
<[9]> ClassSix body
<[500]> MetaAleph.__init__:inner_2
    
<[11]> ClassThree tests .............................. # 执行被类装饰器的类
<[300]> deco_alpha:inner_1 # method_z方法被替换成inner_1方法
<[12]> ClassFour tests .............................. # 继承ClassThree
<[5]> ClassFour.method_y # 不受装饰器的影响
<[13]> Classfive tests .............................. # 继承MetaAleph元类
<[7]> ClassFive.__init__
<[600]> MetaAleph.__init__:inner_2 # MetaAleph类的__init__方法把method_z方法替换成inner_2函数
<[14]> ClassSix tests .............................. # 继承ClassFive
<[7]> ClassFive.__init__
<[600]> MetaAleph.__init__:inner_2 # 与父类相同,method_z方法被替换成inner_2函数
<[15]> evaltime_meta module end # evaltime_meta 结束

定制描述符的元类

  • prepare 使字段和属性按顺序映射
class EntityMeta(type):
    """元类"""
    
    @classmethod
    def __prepare__(cls, name, bases):
        return collections.OrderedDict()
    
    def __init__(cls, name, bases, attr_dict):
        super().__init__(name, bases, attr_dict)
        cls._field_names = []
        for key, attr in attr_dict.items():
            if isinstance(attr, Validated):
                type_name = type(attr).__name__
                attr.storage_name = '_{}#{}'.format(type_name, key)
                cls._field_names.append(key)
                
class Entity(metaclass=EntityMeta):
    """实体"""
    
    @classmethod
    def field_names(cls):
        for name in cls._field_names:
            yield name
  • 继承元类,无需关心机体实现
class LineItem(Entity):
    description = NonBlank()
    weight = Quantity()
    price = Quantity()
    
    def __init__(self, description, weight, price):
        self.description = description
        self.weight = weight
        self.price = price
        
    def subtotal(self):
        return self.weight * self.price
    
for name in LineItem.field_names():
    print(name)
    

类作对象

  • 类属性
LineItem.__mro__
LineItem.__class__
LineItem.__name__

LineItem.__bases__ # 类的基类
LineItem.__qualname__ # 限定名称
Entity.__subclass__() # 直接子类
Entity.mro() # 超类元组

dir(LineItem)

总结

  • type当类使用时,传入三个参数可以新建一个类,name、bases和dict,类名、基类和属性名:值
  • 元类在运行时创建或定制类
  • 类工厂,以及类装饰器
  • 类和函数,在导入时和运行时的执行顺序
  • 类属性
  • 最后一章了

流畅的Python

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值