python之元编程

一、什么是元编程

元编程是一种编写计算机程序的技术,这些程序可以将自己看作数据,因此你可以在运行时对它进行内省、生成和/或修改。

Python在语言层面对函数、类等基本类型提供了内省及实时创建和修改的能力;我们可以使用装饰器向现有的函数、方法或类添加附加功能;同时我们也可以通过修改一些特殊的方法来变更类的行为;

二、使用的例子

import json str = r'''{ "name":"mango", "age": 30, "address":{ "city":"beijing" }, "schools":["xiaoxue","zhongxue"], "sons":[ { "name":"qing" } ] }''' obj = json.loads(str) print(type(obj)) print(obj.get('name')) # <class 'dict'> # mango 

三、通过__getattr__响应动态字段的获取

__getattr__是一个实例方法,适用于访问未定义的属性的时候调用,即该属性在实例中以及对应的类的基类以及祖先类中都不存在的时候调用;

获取字段值的时候,我们先检测对应的字段是否存在,如果不存在则抛出异常;如果字段存在,则检测字段类型并决定是否对嵌套结构进行处理;

import json from collections import abc def loadJsonStr(): str = r'''{ "name":"mango", "age": 30, "address":{ "city":"beijing" }, "schools":["xiaoxue","zhongxue"], "sons":[ { "name":"qing" } ] }''' result = json.loads(str) return result; class JsonObject: def __init__(self, jsondict): self._data = dict(jsondict) def __getattr__(self, name): if name in self._data: val = self._data.get(name) if isinstance(val, abc.Mapping) or isinstance(val, abc.MutableSequence): return self.initinner(val) else: return val else: raise AttributeError(f"{name} field does not exist") def initinner(self, obj): if isinstance(obj, abc.Mapping): return self.__class__(obj) elif isinstance(obj,abc.MutableSequence): return [self.initinner(item) for item in obj] else: return obj jobj = JsonObject(loadJsonStr()) print(jobj.name) print(jobj.address) print(jobj.address.city) print(jobj.schools) print(jobj.sons[0].name) print(jobj.noField) # mango # <__main__.JsonObject object at 0x7ff7eac1cee0> # beijing # ['xiaoxue', 'zhongxue'] # qing # AttributeError: noField field does not exist 

五、使用__new__动态创建对象

我们通常把__init__称为构造方法,但是其实用于构建实例的是__new__:这是一个必须返回一个实例的类方法。返回的实例会作为第一个参数(即self)传给__init__方法。因为调用__init__方法时要传入实例,而且禁止返回任何值,所以__init__方法其实是“初始化方法”。真正的构造方法是__new__。我们可以在构造函数中国完成对JSon字段值的解析处理;

import json from collections import abc def loadJsonStr(): str = r'''{ "name":"mango", "age": 30, "address":{ "city":"beijing" }, "schools":["xiaoxue","zhongxue"], "sons":[ { "name":"qing" } ] }''' result = json.loads(str) return result; class JsonObject: def __new__(cls, args, **kwargs): obj = args if isinstance(obj, abc.Mapping): return super().__new__(cls) elif isinstance(obj,abc.MutableSequence): return [cls(item) for item in obj] else: return obj def __init__(self, jsondict): self._data = dict(jsondict) def __getattr__(self, name): if name in self._data: val = self._data.get(name) if isinstance(val, abc.Mapping) or isinstance(val, abc.MutableSequence): return self.__class__(val) else: return val else: raise AttributeError(f"{name} field does not exist") jobj = JsonObject(loadJsonStr()) print(jobj.name) print(jobj.address) print(jobj.address.city) print(jobj.schools) print(jobj.sons[0].name) print(jobj.noField) # mango # <__main__.JsonObject object at 0x7ff7eac1cee0> # beijing # ['xiaoxue', 'zhongxue'] # qing # AttributeError: noField field does not exist 

六、使用property装饰器添加校验逻辑

我们可以利用Python提供的property属性,为数据字段添加校验逻辑,从而可以避免调用方的变更;虽然property装饰器定义在类上,但是编译器会首先在类上查找,如果找不到才会从类的实例上查找;

import sys class OrderItem: def __init__(self, desc, count, price): self.desc = desc self.count = count self.price = price def subtotal(self): return self.count * self.price @property def price(self): print(f'{sys._getframe().f_code.co_name} getter') return self._price @price.setter def price(self, val): print(f'{sys._getframe().f_code.co_name} setter') if val > 0: self._price = val else: raise ValueError('price must be > 0') @property def count(self): print(f'{sys._getframe().f_code.co_name} getter') return self._count @count.setter def count(self, val): print(f'{sys._getframe().f_code.co_name} setter') if val > 0: self._count = val else: raise ValueError('count must be > 0') pbook = OrderItem('python books', 1, 50) print(pbook.subtotal()) print(OrderItem.price) print(OrderItem.price.setter) print(OrderItem.price.getter) print(vars(pbook)) jbook = OrderItem('java books', 0, 50) print(jbook.subtotal()) # count setter # price setter # count getter # price getter # 50 # <property object at 0x7ffa8ddf8a90> # <built-in method setter of property object at 0x7ffa8ddf8a90> # <built-in method getter of property object at 0x7ffa8ddf8a90> # {'desc': 'python books', '_count': 1, '_price': 50} # count setter # ValueError: count must be > 0 

可以将创建字段的逻辑抽取出来作为公用的方法

import sys def buildVolidateField(name): _name = f'_{name}' def getter(obj): return obj.__dict__.get(_name) def setter(obj, value): if value > 0: obj.__dict__[_name]= value else: raise ValueError(f'{name} must be > 0') return property(getter, setter) class OrderItem: price = buildVolidateField('price') count = buildVolidateField('count') def __init__(self, desc, count, price): self.desc = desc self.count = count self.price = price def subtotal(self): return self.count * self.price pbook = OrderItem('python books', 1, 50) print(pbook.subtotal()) print(OrderItem.price) print(OrderItem.price.setter) print(OrderItem.price.getter) print(vars(pbook)) jbook = OrderItem('java books', 0, 50) print(jbook.subtotal()) # 50 # <property object at 0x7fbc90cfdd60> # <built-in method setter of property object at 0x7fbc90cfdd60> # <built-in method getter of property object at 0x7fbc90cfdd60> # {'desc': 'python books', '_count': 1, '_price': 50} # ValueError: count must be > 0 

七、使用描述符类实现字段校验逻辑

描述符是实现了特定协议的类,这个协议包括__get__、__set__和__delete__方法。property类实现了完整的描述符协议。通常,可以只实现部分协议。其实,我们在真实的代码中见到的大多数描述符只实现了__get__和__set__方法,还有很多只实现了其中的一个。

import sys class VolidateDescr: def __init__(self, name): self.name = f'_{name}' def __set__(self, instance, value): if value > 0: instance.__dict__[self.name] = value else: raise ValueError(f'{self.name} must be > 0') def __get__(self, instance, default): if instance is None: return self; else: return instance.__dict__[self.name] class OrderItem: price = VolidateDescr('price') count = VolidateDescr('count') def __init__(self, desc, count, price): self.desc = desc self.count = count self.price = price def subtotal(self): return self.count * self.price pbook = OrderItem('python books', 1, 50) print(pbook.subtotal()) print(OrderItem.price) print(OrderItem.price.__set__) print(OrderItem.price.__get__) print(vars(pbook)) jbook = OrderItem('java books', 0, 50) print(jbook.subtotal()) # 50 # <__main__.VolidateDescr object at 0x7f162d0ac9a0> # <bound method VolidateDescr.__set__ of <__main__.VolidateDescr object at 0x7f162d0ac9a0>> # <bound method VolidateDescr.__get__ of <__main__.VolidateDescr object at 0x7f162d0ac9a0>> # {'desc': 'python books', '_count': 1, '_price': 50} # ValueError: _count must be > 0 

目前只是两个数字字段添加了校验,接下来为desc字符串字段添加非空校验;两种数据类型字段只有校验的差异,我们将校验逻辑跟字段的访问控制进行抽离,分别实现两个具体的校验类;

import abc class FieldDescr: _countor = 0 def __init__(self): self.name = f'_{self.__class__.__name__}_{self.__class__._countor}' self.__class__._countor += 1 def __set__(self, instance, value): setattr(instance, self.name, value) def __get__(self, instance, owner): if instance is None: return self else: return getattr(instance, self.name) class Validated(FieldDescr): def __set__(self, instance, value): value = self.validate(instance, value) super().__set__(instance, value) @abc.abstractmethod def validate(self, instance, value): '''this is abstract method''' class GreatZeroIntField(Validated): def validate(self, instance, value): if value <= 0: raise ValueError(f'{self.name} value must be > 0') return value class NoEmptyStrField(Validated): def validate(self, instance, value): value = value.strip() if len(value) == 0: raise ValueError('value cant not be empty or blank') return value class OrderItem: descr = NoEmptyStrField() price = GreatZeroIntField() count = GreatZeroIntField() def __init__(self, descr, price, count): self.descr = descr self.price = price self.count = count def subtotal(self): return self.count * self.price pbook = OrderItem('python books', 1, 50) print(pbook.subtotal()) print(OrderItem.price) print(OrderItem.price.__set__) print(OrderItem.price.__get__) print(vars(pbook)) jbook = OrderItem('java books', 0, 50) print(jbook.subtotal()) # 50 # <__main__.GreatZeroIntField object at 0x7fa2eb37fd00> # <bound method Validated.__set__ of <__main__.GreatZeroIntField object at 0x7fa2eb37fd00>> # <bound method FieldDescr.__get__ of <__main__.GreatZeroIntField object at 0x7fa2eb37fd00>> # {'_NoEmptyStrField_0': 'python books', '_GreatZeroIntField_0': 1, '_GreatZeroIntField_1': 50} # ValueError: _GreatZeroIntField_0 value must be > 0 

到现在我们已经封装自动生成特性,自动生成的数据字段的名字并不能很好的跟类上对应的特性名称对应上;接下来通过类装饰器和元类来定制数据字段的名字;

类装饰器在编译器编译完类之后执行,这个时候类上的特性已经生成完毕,我们可以遍历类的__dict__,找到对应的特性并修改其name字段的值即可;

import abc def renamePrivateField(cls): for key,value in cls.__dict__.items(): if isinstance(value, Validated): value.name = f'_{value.__class__.__name__}_{key}' return cls class FieldDescr: _countor = 0 def __init__(self): self.name = f'_{self.__class__.__name__}_{self.__class__._countor}' self.__class__._countor += 1 def __set__(self, instance, value): setattr(instance, self.name, value) def __get__(self, instance, owner): if instance is None: return self else: return getattr(instance, self.name) class Validated(FieldDescr): def __set__(self, instance, value): value = self.validate(instance, value) super().__set__(instance, value) @abc.abstractmethod def validate(self, instance, value): '''this is abstract method''' class GreatZeroIntField(Validated): def validate(self, instance, value): if value <= 0: raise ValueError(f'{self.name} value must be > 0') return value class NoEmptyStrField(Validated): def validate(self, instance, value): value = value.strip() if len(value) == 0: raise ValueError('value cant not be empty or blank') return value @renamePrivateField class OrderItem: descr = NoEmptyStrField() price = GreatZeroIntField() count = GreatZeroIntField() def __init__(self, descr, price, count): self.descr = descr self.price = price self.count = count def subtotal(self): return self.count * self.price pbook = OrderItem('python books', 1, 50) print(pbook.subtotal()) print(OrderItem.price) print(OrderItem.price.name) print(OrderItem.price.__set__) print(OrderItem.price.__get__) print(vars(pbook)) # 50 # <__main__.GreatZeroIntField object at 0x7f23e67bf2b0> # _GreatZeroIntField_price # <bound method Validated.__set__ of <__main__.GreatZeroIntField object at 0x7f23e67bf2b0>> # <bound method FieldDescr.__get__ of <__main__.GreatZeroIntField object at 0x7f23e67bf2b0>> # {'_NoEmptyStrField_descr': 'python books', '_GreatZeroIntField_price': 1, '_GreatZeroIntField_count': 50} 

由于类装饰器在类编译完整之后直接执行,可能会出现被子类覆盖的情况,元类可以很好的解决这个问题

import abc class FieldDescr: _countor = 0 def __init__(self): self.name = f'_{self.__class__.__name__}_{self.__class__._countor}' self.__class__._countor += 1 def __set__(self, instance, value): setattr(instance, self.name, value) def __get__(self, instance, owner): if instance is None: return self else: return getattr(instance, self.name) class Validated(FieldDescr): def __set__(self, instance, value): value = self.validate(instance, value) super().__set__(instance, value) @abc.abstractmethod def validate(self, instance, value): '''this is abstract method''' class GreatZeroIntField(Validated): def validate(self, instance, value): if value <= 0: raise ValueError(f'{self.name} value must be > 0') return value class NoEmptyStrField(Validated): def validate(self, instance, value): value = value.strip() if len(value) == 0: raise ValueError('value cant not be empty or blank') return value class renamePrivateFieldMeta(type): def __init__(cls, name, bases, attr_dict): super().__init__(name, bases, attr_dict) for key, value in cls.__dict__.items(): if isinstance(value, Validated): value.name = f'_{value.__class__.__name__}_{key}' class OrderEntity(metaclass=renamePrivateFieldMeta): '''rename entity''' class OrderItem(OrderEntity): descr = NoEmptyStrField() price = GreatZeroIntField() count = GreatZeroIntField() def __init__(self, descr, price, count): self.descr = descr self.price = price self.count = count def subtotal(self): return self.count * self.price pbook = OrderItem('python books', 1, 50) print(pbook.subtotal()) print(OrderItem.price) print(OrderItem.price.name) print(OrderItem.price.__set__) print(OrderItem.price.__get__) print(vars(pbook)) # 50 # <__main__.GreatZeroIntField object at 0x7f393be8c070> # _GreatZeroIntField_price # <bound method Validated.__set__ of <__main__.GreatZeroIntField object at 0x7f393be8c070>> # <bound method FieldDescr.__get__ of <__main__.GreatZeroIntField object at 0x7f393be8c070>> # {'_NoEmptyStrField_descr': 'python books', '_GreatZeroIntField_price': 1, '_GreatZeroIntField_count': 50} 
本网页由快兔兔AI采集器生成,目的为演示采集效果,若侵权请及时联系删除。

原文链接:https://www.cnblogs.com/wufengtinghai/p/15621829.html

更多内容