一、什么是元编程

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

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

二、使用的例子

  1. 面对一个复杂多变的json数据结构,虽然Python提供了处理JSon数据的API,但是返回的类型是dict,使用非常不方便不友好;接下来通过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
  1. 面向对象变成提倡封装数字字段,通过访问控制器来控制数据字段的校验;接下来通过python提供的元编程能力进行实现;

三、通过__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
  1. 定制数据字段的名字

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

类装饰器在编译器编译完类之后执行,这个时候类上的特性已经生成完毕,我们可以遍历类的__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}

python之元编程的更多相关文章

  1. Python的元编程案例

    Python的元编程案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是元编程 元编程概念来自LISP和smalltalk. 我们写程序是直接写代码,是否能够用代码来生成 ...

  2. Python类元编程

    类元编程是指在运行时创建或定制类.在Python中,类是一等对象,因此任何时候都可以使用函数创建新类,而无需用class关键字.类装饰器也是函数,不过能够审查.修改,甚至把被装饰的类替换成其他类.元类 ...

  3. python之元编程(元类实例)

    本实例是元类实例,功能是记录该的子类的类名,并以树状结构展示子类的类名. RegisterClasses继承自type,提供的功能是在__init__接口,为类创建了childrens的集合,并类名保 ...

  4. Python类元编程初探

    在<流畅的Python>一书中提到: Classes are first-class object in Python, so a function can be used to crea ...

  5. Python元编程

    简单定义"元编程是一种编写计算机程序的技术,这些程序可以将自己看做数据,因此你可以在运行时对它进行内审.生成和/或修改",本博参考<<Python高级编程>> ...

  6. python元编程(metaclass)

    Python元编程就是使用metaclass技术进行编程,99%的情况下不会使用,了解即可. Python中的类和对象 对于学习Python和使用Python的同学,你是否好奇过Python中的对象究 ...

  7. Python 元编程 - 装饰器

    Python 中提供了一个叫装饰器的特性,用于在不改变原始对象的情况下,增加新功能或行为. 这也属于 Python "元编程" 的一部分,在编译时一个对象去试图修改另一个对象的信息 ...

  8. PYTHON黑帽编程 4.1 SNIFFER(嗅探器)之数据捕获(下)

    上一节(<4.1 SNIFFER(嗅探器)之数据捕获(上)>)中, 我们讲解了通过Raw Socket的方式来编写Sniffer的基本方法. 本节我们继续来编写Sniffer,只不过使用现 ...

  9. Java元编程及其应用

    首先,我们且不说元编程是什么,他能做什么.我们先来谈谈生产力. 同样是实现一个投票系统,一个是python程序员,基于django-framework,用了半小时就搭建了一个完整系统,另外一个是标准的 ...

随机推荐

  1. [源码解析]PyTorch如何实现前向传播(2) --- 基础类(下)

    [源码解析]PyTorch如何实现前向传播(2) --- 基础类(下) 目录 [源码解析]PyTorch如何实现前向传播(2) --- 基础类(下) 0x00 摘要 0x01 前文回顾 0x02 Te ...

  2. logging的基本使用

    logging模块打印log的时候主要有一下几个,级别顺序:CRITICAL>ERROR>WARNING>INFO>DEBUG: 1.日志输出到file: import log ...

  3. k8s 关于Job与Cronjob

    在Kubernetes 中通过创建工作负载资源 Job 可完成大型计算以及一些批处理任务.比如 Job 转码文件.获取部分文件和目录,机器学习中的训练任务等.这篇小作文我们一起来了解 k8s 中关于 ...

  4. Noip模拟6 2021.6.10

    T1 辣鸡 首先吐嘈一下,这题的测试点就离谱,不说了,附上我65分代码: 1 #include<bits/stdc++.h> 2 #define int long long 3 using ...

  5. 热身训练1 Sequence

    http://acm.hdu.edu.cn/showproblem.php?pid=6 分析: 这道题,全都是1e9,所以我们很容易想到"矩阵快速幂". 假如说我们没有后面那个&q ...

  6. 对SQLServer错误使用聚集索引的优化案例(千万级数据量)

    前言: 半个月前发了文章 SQLServer聚集索引导致的插入性能低 终于等到生产环境休整半天,这篇文章是对前文的实际操作. 以下正文开始: 异常:近期发现偶尔有新数据插入超时. 分析:插入条码有多种 ...

  7. jQuery中onload与ready区别

    onload和ready的区别document.ready和onload的区别为:加载程度不同.执行次数不同.执行速度不同.1.加载程度不同 document.ready:是DOM结构绘制完毕后就执行 ...

  8. 基于 OSPF 路由的邻居邻接关系发现实践

    1.实验目的 理解 OSPF 邻居关系和 OSPF 邻接关系的含义及差别 观察 OSPF 邻居邻接关系的建立过程 观察 OSPF 链路状态数据库的同步过程 2.实验原理 OSPF 网络中,路由器在发送 ...

  9. 攻防世界 WEB 高手进阶区 NSCTF web2 Writeup

    攻防世界 WEB 高手进阶区 NSCTF web2 Writeup 题目介绍 题目考点 php基本函数语法 加密解密函数 base64_decode().str_rot13() 字符串反转函数 str ...

  10. 【AI测试】人工智能 (AI) 测试--开篇

    人工智能测试 什么是人工智能,人工智能是怎么测试的.可能是大家一开始最想了解的.大家看图中关于人工智能的定义.通俗点来说呢,就是 让机器实现原来只有人类才能完成的任务:比如看懂照片,听懂说话,思考等等 ...