一、什么是元编程

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

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

二、使用的例子

  1. 面对一个复杂多变的json数据结构,虽然Python提供了处理JSon数据的API,但是返回的类型是dict,使用非常不方便不友好;接下来通过Python提供的元编程的能力,来实现一个类似对象层次结构的访问方式;
  1. import json
  2. str = r'''{
  3. "name":"mango",
  4. "age": 30,
  5. "address":{
  6. "city":"beijing"
  7. },
  8. "schools":["xiaoxue","zhongxue"],
  9. "sons":[
  10. {
  11. "name":"qing"
  12. }
  13. ]
  14. }'''
  15. obj = json.loads(str)
  16. print(type(obj))
  17. print(obj.get('name'))
  18. # <class 'dict'>
  19. # mango
  1. 面向对象变成提倡封装数字字段,通过访问控制器来控制数据字段的校验;接下来通过python提供的元编程能力进行实现;

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

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

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

  1. import json
  2. from collections import abc
  3. def loadJsonStr():
  4. str = r'''{
  5. "name":"mango",
  6. "age": 30,
  7. "address":{
  8. "city":"beijing"
  9. },
  10. "schools":["xiaoxue","zhongxue"],
  11. "sons":[
  12. {
  13. "name":"qing"
  14. }
  15. ]
  16. }'''
  17. result = json.loads(str)
  18. return result;
  19. class JsonObject:
  20. def __init__(self, jsondict):
  21. self._data = dict(jsondict)
  22. def __getattr__(self, name):
  23. if name in self._data:
  24. val = self._data.get(name)
  25. if isinstance(val, abc.Mapping) or isinstance(val, abc.MutableSequence):
  26. return self.initinner(val)
  27. else:
  28. return val
  29. else:
  30. raise AttributeError(f"{name} field does not exist")
  31. def initinner(self, obj):
  32. if isinstance(obj, abc.Mapping):
  33. return self.__class__(obj)
  34. elif isinstance(obj,abc.MutableSequence):
  35. return [self.initinner(item) for item in obj]
  36. else:
  37. return obj
  38. jobj = JsonObject(loadJsonStr())
  39. print(jobj.name)
  40. print(jobj.address)
  41. print(jobj.address.city)
  42. print(jobj.schools)
  43. print(jobj.sons[0].name)
  44. print(jobj.noField)
  45. # mango
  46. # <__main__.JsonObject object at 0x7ff7eac1cee0>
  47. # beijing
  48. # ['xiaoxue', 'zhongxue']
  49. # qing
  50. # AttributeError: noField field does not exist

五、使用__new__动态创建对象

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

  1. import json
  2. from collections import abc
  3. def loadJsonStr():
  4. str = r'''{
  5. "name":"mango",
  6. "age": 30,
  7. "address":{
  8. "city":"beijing"
  9. },
  10. "schools":["xiaoxue","zhongxue"],
  11. "sons":[
  12. {
  13. "name":"qing"
  14. }
  15. ]
  16. }'''
  17. result = json.loads(str)
  18. return result;
  19. class JsonObject:
  20. def __new__(cls, args, **kwargs):
  21. obj = args
  22. if isinstance(obj, abc.Mapping):
  23. return super().__new__(cls)
  24. elif isinstance(obj,abc.MutableSequence):
  25. return [cls(item) for item in obj]
  26. else:
  27. return obj
  28. def __init__(self, jsondict):
  29. self._data = dict(jsondict)
  30. def __getattr__(self, name):
  31. if name in self._data:
  32. val = self._data.get(name)
  33. if isinstance(val, abc.Mapping) or isinstance(val, abc.MutableSequence):
  34. return self.__class__(val)
  35. else:
  36. return val
  37. else:
  38. raise AttributeError(f"{name} field does not exist")
  39. jobj = JsonObject(loadJsonStr())
  40. print(jobj.name)
  41. print(jobj.address)
  42. print(jobj.address.city)
  43. print(jobj.schools)
  44. print(jobj.sons[0].name)
  45. print(jobj.noField)
  46. # mango
  47. # <__main__.JsonObject object at 0x7ff7eac1cee0>
  48. # beijing
  49. # ['xiaoxue', 'zhongxue']
  50. # qing
  51. # AttributeError: noField field does not exist

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

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

  1. import sys
  2. class OrderItem:
  3. def __init__(self, desc, count, price):
  4. self.desc = desc
  5. self.count = count
  6. self.price = price
  7. def subtotal(self):
  8. return self.count * self.price
  9. @property
  10. def price(self):
  11. print(f'{sys._getframe().f_code.co_name} getter')
  12. return self._price
  13. @price.setter
  14. def price(self, val):
  15. print(f'{sys._getframe().f_code.co_name} setter')
  16. if val > 0:
  17. self._price = val
  18. else:
  19. raise ValueError('price must be > 0')
  20. @property
  21. def count(self):
  22. print(f'{sys._getframe().f_code.co_name} getter')
  23. return self._count
  24. @count.setter
  25. def count(self, val):
  26. print(f'{sys._getframe().f_code.co_name} setter')
  27. if val > 0:
  28. self._count = val
  29. else:
  30. raise ValueError('count must be > 0')
  31. pbook = OrderItem('python books', 1, 50)
  32. print(pbook.subtotal())
  33. print(OrderItem.price)
  34. print(OrderItem.price.setter)
  35. print(OrderItem.price.getter)
  36. print(vars(pbook))
  37. jbook = OrderItem('java books', 0, 50)
  38. print(jbook.subtotal())
  39. # count setter
  40. # price setter
  41. # count getter
  42. # price getter
  43. # 50
  44. # <property object at 0x7ffa8ddf8a90>
  45. # <built-in method setter of property object at 0x7ffa8ddf8a90>
  46. # <built-in method getter of property object at 0x7ffa8ddf8a90>
  47. # {'desc': 'python books', '_count': 1, '_price': 50}
  48. # count setter
  49. # ValueError: count must be > 0

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

  1. import sys
  2. def buildVolidateField(name):
  3. _name = f'_{name}'
  4. def getter(obj):
  5. return obj.__dict__.get(_name)
  6. def setter(obj, value):
  7. if value > 0:
  8. obj.__dict__[_name]= value
  9. else:
  10. raise ValueError(f'{name} must be > 0')
  11. return property(getter, setter)
  12. class OrderItem:
  13. price = buildVolidateField('price')
  14. count = buildVolidateField('count')
  15. def __init__(self, desc, count, price):
  16. self.desc = desc
  17. self.count = count
  18. self.price = price
  19. def subtotal(self):
  20. return self.count * self.price
  21. pbook = OrderItem('python books', 1, 50)
  22. print(pbook.subtotal())
  23. print(OrderItem.price)
  24. print(OrderItem.price.setter)
  25. print(OrderItem.price.getter)
  26. print(vars(pbook))
  27. jbook = OrderItem('java books', 0, 50)
  28. print(jbook.subtotal())
  29. # 50
  30. # <property object at 0x7fbc90cfdd60>
  31. # <built-in method setter of property object at 0x7fbc90cfdd60>
  32. # <built-in method getter of property object at 0x7fbc90cfdd60>
  33. # {'desc': 'python books', '_count': 1, '_price': 50}
  34. # ValueError: count must be > 0

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

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

  1. import sys
  2. class VolidateDescr:
  3. def __init__(self, name):
  4. self.name = f'_{name}'
  5. def __set__(self, instance, value):
  6. if value > 0:
  7. instance.__dict__[self.name] = value
  8. else:
  9. raise ValueError(f'{self.name} must be > 0')
  10. def __get__(self, instance, default):
  11. if instance is None:
  12. return self;
  13. else:
  14. return instance.__dict__[self.name]
  15. class OrderItem:
  16. price = VolidateDescr('price')
  17. count = VolidateDescr('count')
  18. def __init__(self, desc, count, price):
  19. self.desc = desc
  20. self.count = count
  21. self.price = price
  22. def subtotal(self):
  23. return self.count * self.price
  24. pbook = OrderItem('python books', 1, 50)
  25. print(pbook.subtotal())
  26. print(OrderItem.price)
  27. print(OrderItem.price.__set__)
  28. print(OrderItem.price.__get__)
  29. print(vars(pbook))
  30. jbook = OrderItem('java books', 0, 50)
  31. print(jbook.subtotal())
  32. # 50
  33. # <__main__.VolidateDescr object at 0x7f162d0ac9a0>
  34. # <bound method VolidateDescr.__set__ of <__main__.VolidateDescr object at 0x7f162d0ac9a0>>
  35. # <bound method VolidateDescr.__get__ of <__main__.VolidateDescr object at 0x7f162d0ac9a0>>
  36. # {'desc': 'python books', '_count': 1, '_price': 50}
  37. # ValueError: _count must be > 0

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

  1. import abc
  2. class FieldDescr:
  3. _countor = 0
  4. def __init__(self):
  5. self.name = f'_{self.__class__.__name__}_{self.__class__._countor}'
  6. self.__class__._countor += 1
  7. def __set__(self, instance, value):
  8. setattr(instance, self.name, value)
  9. def __get__(self, instance, owner):
  10. if instance is None:
  11. return self
  12. else:
  13. return getattr(instance, self.name)
  14. class Validated(FieldDescr):
  15. def __set__(self, instance, value):
  16. value = self.validate(instance, value)
  17. super().__set__(instance, value)
  18. @abc.abstractmethod
  19. def validate(self, instance, value):
  20. '''this is abstract method'''
  21. class GreatZeroIntField(Validated):
  22. def validate(self, instance, value):
  23. if value <= 0:
  24. raise ValueError(f'{self.name} value must be > 0')
  25. return value
  26. class NoEmptyStrField(Validated):
  27. def validate(self, instance, value):
  28. value = value.strip()
  29. if len(value) == 0:
  30. raise ValueError('value cant not be empty or blank')
  31. return value
  32. class OrderItem:
  33. descr = NoEmptyStrField()
  34. price = GreatZeroIntField()
  35. count = GreatZeroIntField()
  36. def __init__(self, descr, price, count):
  37. self.descr = descr
  38. self.price = price
  39. self.count = count
  40. def subtotal(self):
  41. return self.count * self.price
  42. pbook = OrderItem('python books', 1, 50)
  43. print(pbook.subtotal())
  44. print(OrderItem.price)
  45. print(OrderItem.price.__set__)
  46. print(OrderItem.price.__get__)
  47. print(vars(pbook))
  48. jbook = OrderItem('java books', 0, 50)
  49. print(jbook.subtotal())
  50. # 50
  51. # <__main__.GreatZeroIntField object at 0x7fa2eb37fd00>
  52. # <bound method Validated.__set__ of <__main__.GreatZeroIntField object at 0x7fa2eb37fd00>>
  53. # <bound method FieldDescr.__get__ of <__main__.GreatZeroIntField object at 0x7fa2eb37fd00>>
  54. # {'_NoEmptyStrField_0': 'python books', '_GreatZeroIntField_0': 1, '_GreatZeroIntField_1': 50}
  55. # ValueError: _GreatZeroIntField_0 value must be > 0
  1. 定制数据字段的名字

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

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

  1. import abc
  2. def renamePrivateField(cls):
  3. for key,value in cls.__dict__.items():
  4. if isinstance(value, Validated):
  5. value.name = f'_{value.__class__.__name__}_{key}'
  6. return cls
  7. class FieldDescr:
  8. _countor = 0
  9. def __init__(self):
  10. self.name = f'_{self.__class__.__name__}_{self.__class__._countor}'
  11. self.__class__._countor += 1
  12. def __set__(self, instance, value):
  13. setattr(instance, self.name, value)
  14. def __get__(self, instance, owner):
  15. if instance is None:
  16. return self
  17. else:
  18. return getattr(instance, self.name)
  19. class Validated(FieldDescr):
  20. def __set__(self, instance, value):
  21. value = self.validate(instance, value)
  22. super().__set__(instance, value)
  23. @abc.abstractmethod
  24. def validate(self, instance, value):
  25. '''this is abstract method'''
  26. class GreatZeroIntField(Validated):
  27. def validate(self, instance, value):
  28. if value <= 0:
  29. raise ValueError(f'{self.name} value must be > 0')
  30. return value
  31. class NoEmptyStrField(Validated):
  32. def validate(self, instance, value):
  33. value = value.strip()
  34. if len(value) == 0:
  35. raise ValueError('value cant not be empty or blank')
  36. return value
  37. @renamePrivateField
  38. class OrderItem:
  39. descr = NoEmptyStrField()
  40. price = GreatZeroIntField()
  41. count = GreatZeroIntField()
  42. def __init__(self, descr, price, count):
  43. self.descr = descr
  44. self.price = price
  45. self.count = count
  46. def subtotal(self):
  47. return self.count * self.price
  48. pbook = OrderItem('python books', 1, 50)
  49. print(pbook.subtotal())
  50. print(OrderItem.price)
  51. print(OrderItem.price.name)
  52. print(OrderItem.price.__set__)
  53. print(OrderItem.price.__get__)
  54. print(vars(pbook))
  55. # 50
  56. # <__main__.GreatZeroIntField object at 0x7f23e67bf2b0>
  57. # _GreatZeroIntField_price
  58. # <bound method Validated.__set__ of <__main__.GreatZeroIntField object at 0x7f23e67bf2b0>>
  59. # <bound method FieldDescr.__get__ of <__main__.GreatZeroIntField object at 0x7f23e67bf2b0>>
  60. # {'_NoEmptyStrField_descr': 'python books', '_GreatZeroIntField_price': 1, '_GreatZeroIntField_count': 50}

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

  1. import abc
  2. class FieldDescr:
  3. _countor = 0
  4. def __init__(self):
  5. self.name = f'_{self.__class__.__name__}_{self.__class__._countor}'
  6. self.__class__._countor += 1
  7. def __set__(self, instance, value):
  8. setattr(instance, self.name, value)
  9. def __get__(self, instance, owner):
  10. if instance is None:
  11. return self
  12. else:
  13. return getattr(instance, self.name)
  14. class Validated(FieldDescr):
  15. def __set__(self, instance, value):
  16. value = self.validate(instance, value)
  17. super().__set__(instance, value)
  18. @abc.abstractmethod
  19. def validate(self, instance, value):
  20. '''this is abstract method'''
  21. class GreatZeroIntField(Validated):
  22. def validate(self, instance, value):
  23. if value <= 0:
  24. raise ValueError(f'{self.name} value must be > 0')
  25. return value
  26. class NoEmptyStrField(Validated):
  27. def validate(self, instance, value):
  28. value = value.strip()
  29. if len(value) == 0:
  30. raise ValueError('value cant not be empty or blank')
  31. return value
  32. class renamePrivateFieldMeta(type):
  33. def __init__(cls, name, bases, attr_dict):
  34. super().__init__(name, bases, attr_dict)
  35. for key, value in cls.__dict__.items():
  36. if isinstance(value, Validated):
  37. value.name = f'_{value.__class__.__name__}_{key}'
  38. class OrderEntity(metaclass=renamePrivateFieldMeta):
  39. '''rename entity'''
  40. class OrderItem(OrderEntity):
  41. descr = NoEmptyStrField()
  42. price = GreatZeroIntField()
  43. count = GreatZeroIntField()
  44. def __init__(self, descr, price, count):
  45. self.descr = descr
  46. self.price = price
  47. self.count = count
  48. def subtotal(self):
  49. return self.count * self.price
  50. pbook = OrderItem('python books', 1, 50)
  51. print(pbook.subtotal())
  52. print(OrderItem.price)
  53. print(OrderItem.price.name)
  54. print(OrderItem.price.__set__)
  55. print(OrderItem.price.__get__)
  56. print(vars(pbook))
  57. # 50
  58. # <__main__.GreatZeroIntField object at 0x7f393be8c070>
  59. # _GreatZeroIntField_price
  60. # <bound method Validated.__set__ of <__main__.GreatZeroIntField object at 0x7f393be8c070>>
  61. # <bound method FieldDescr.__get__ of <__main__.GreatZeroIntField object at 0x7f393be8c070>>
  62. # {'_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. NX二次开发 克隆

    NXOpen.UF.UFSession theUfSession = NXOpen.UF.UFSession.GetUFSession(); try { //初始化 NXOpen.UF.UFClone ...

  2. 全场景效能平台猪齿鱼常用的前端css实现方案

    ​ 居中 最常用的height + line-height,以及margin:0 auto的居中方式就不再阐述,以下介绍两种容错性高的实现方案. flex布局实现 ​ 猪齿鱼前端日常开发中,我们多以f ...

  3. CQL和SQL的CRUD操作比较

    数据进行CRUD操作时,CQL语句和SQL语句的异同之处. 1.建表 2.CRUD语句比较 3.总结 1.建表 在此之前先分别创建两张表,插入数据,用来测试然后进行比较 在SQL数据库里面创建表 在C ...

  4. OO第四次博客作业--第四单元总结及课程总结

    一.总结第四单元两次作业的架构设计 1.1 第一次作业 类图如下: 为了突出类.接口.方法.属性.和参数之间的层次结构关系,我为 Class 和 Interface 和 Operation 分别建立了 ...

  5. Stack2 攻防世界题目分析

    ---XCTF 4th-QCTF-2018 前言,怎么说呢,这题目还是把我折磨的可以的,我一开始是没有看到后面的直接狙击的,只能说呢. 我的不经意间的粗心,破坏了你许多的温柔 1.气的我直接检查保护: ...

  6. 《基于SIR的路边违停行为传播模型研究》

    My Focus: 路边违停 行为的传播模型; 学习基于SIR XXX模型的可行性分析.建立和结论分析 Author: 左忠义,王英英,包蕴 Mind Map:

  7. numpy.zeros()的作用和实操

    numpy.zeros()的作用: 通常是把数组转换成想要的矩阵 numpy.zeros()的使用方法: zeros(shape, dtype=float, order='C') shape:数据尺寸 ...

  8. Ubuntu 16.04 下 旋转显示器屏幕 竖屏显示

    xrandr -o left $ xrandr -o left 向左旋转90度 $ xrandr -o right 向右旋转90度 $ xrandr -o inverted 上下翻转 $ xrandr ...

  9. populating-next-right-pointers-in-each-node-ii leetcode C++

    Follow up for problem "Populating Next Right Pointers in Each Node". What if the given tre ...

  10. 解决CentOS添加新网卡后找不到网卡配置文件,配置多网卡并设置静态路由

    参考文章 https://blog.csdn.net/qq_36512792/article/details/79787649 使用VMware Workstation虚拟机安装好CentOS7虚拟机 ...