翼度科技»论坛 编程开发 python 查看内容

Python 元编程

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
作者:袁首京  原创文章,转载时请保留此声明,并给出原文连接。
元编程并不象它听起来那么时髦和新奇。常用的 decorator 就可以认为是一种元编程。简单来说,元编程就是编写操作代码的代码。
有点绕,是吧?别着急,咱们一点一点来讨论。
注意:本文中的代码适用于 Python 3.3 及以上。
元类

多数编程语言中,一切东西都有类型。Python 也不例外,我们可以用 type() 函数获取任意变量的类型。
  1. num = 23
  2. print("Type of num is:", type(num))
  3. lst = [1, 2, 4]
  4. print("Type of lst is:", type(lst))
  5. name = "Atul"
  6. print("Type of name is:", type(name))
复制代码
执行结果是:
  1. Type of num is: <class 'int'>
  2. Type of lst is: <class 'list'>
  3. Type of name is: <class 'str'>
复制代码
Python 中的所有类型都是由 Class 定义的。这一条与其它编程语言,比如 Java、C++ 等等不同。在那些语言中,int、char、float 之类是基本数据类型,但是在 Python 中,它们是 int 类或 str 类的对象。
象其它 OOP 语言一样,我们可以使用 class 定义新类型:
  1. class Student:
  2.     pass
  3. stu_obj = Student()
  4. print("Type of stu_obj is:", type(stu_obj))
复制代码
执行结果是:
  1. Type of stu_obj is: <class '**main**.Student'>
复制代码
一点儿也不意外,对吧?其实有意外,因为在 Python 中,类也是一个对象,就像任何其他对象一样,它是元类的实例。即一个特殊的类,创建了 Class 这个特殊的类实例。看如下代码:
  1. class Student:
  2.     pass
  3. print("Type of Student class is:", type(Student))
复制代码
执行结果是:
  1. Type of Student class is: <class 'type'>
复制代码
既然类也是一个对象,所以以修改对象相同的方式修改它就顺理成章。如下先定义一个没有任何属性和方法的类,然后在外部为其添加属性和方法:
  1. class test:
  2.     pass
  3. test.x = 45
  4. test.foo = lambda self: print('Hello')
  5. myobj = test()
  6. print(myobj.x)
  7. myobj.foo()
复制代码
执行结果是:
  1. 45
  2. Hello
复制代码
以上过程可以简单概括为:
元类创建类,类创建实例
画个图象这样:
元类 -> 类 -> 实例
因此,我们就可以编写自定义的元类,执行额外的操作或者注入代码,来改变类的生成过程。这在某些场景下很有用,主要是比如有些情况下使用元编程更简单,另一些情况只有元编程才能解决问题。
创建自定义元类

创建自定义元类,有两种方法。第一种是继承 type 元类,并且覆写两个方法:

  • new()
它在 init() 之前调用,生成类实例并返回。我们可以覆盖此方法来控制对象的创建过程。

  • init()
这个不多解释,相信你都明白。
如下是个例子:
  1. class MultiBases(type):
  2.     def __new__(cls, clsname, bases, clsdict):
  3.         if len(bases)>1:
  4.             raise TypeError("Inherited multiple base classes!!!")
  5.         return super().__new__(cls, clsname, bases, clsdict)
  6. class Base(metaclass=MultiBases):
  7.     pass
  8. class A(Base):
  9.     pass
  10. class B(Base):
  11.     pass
  12. class C(A, B):
  13.     pass
复制代码
执行结果是:
  1. Traceback (most recent call last):
  2. File "<stdin>", line 2, in <module>
  3. File "<stdin>", line 8, in **new**
  4. TypeError: Inherited multiple base classes!!!
复制代码
第二种方法是直接使用 type() 函数创建类。这个方法如果只用一个参数调用,它会返回该参数的类型,前文已经描述过。但是使用三个参数调用时,它会创建一个类。这三个参数如下:

  • 类名称;
  • 继承的父类的元组。你没看错,是元组,别忘了 Python 可以多继承;
  • 一个字典。定义类属性和方法;
以下是示例:
  1. def test_method(self):
  2.     print("This is Test class method!")
  3. class Base:
  4.     def myfun(self):
  5.         print("This is inherited method!")
  6. Test = type('Test', (Base, ), dict(x="atul", my_method=test_method))
  7. print("Type of Test class: ", type(Test))
  8. test_obj = Test()
  9. print("Type of test_obj: ", type(test_obj))
  10. test_obj.myfun()
  11. test_obj.my_method()
  12. print(test_obj.x)
复制代码
执行结果是:
  1. Type of Test class: <class 'type'>
  2. Type of test_obj: <class '**main**.Test'>
  3. This is inherited method!
  4. This is Test class method!
  5. atul
复制代码
使用元类解决问题

了解了元类的创建方法后,可以来解决一些实际问题了。例如,如果我们想在每次调用类方法时,都先输出一下它的全限定名,该怎么办呢?
最常用的方法是使用 decorator,象这样:
  1. from functools import wraps
  2. def debug(func):
  3.     @wraps(func)
  4.     def wrapper(*args, **kwargs):
  5.         print("Full name of this method:", func.__qualname__)
  6.         return func(*args, **kwargs)
  7.     return wrapper
  8. def debugmethods(cls):
  9.     for key, val in vars(cls).items():
  10.         if callable(val):
  11.             setattr(cls, key, debug(val))
  12.     return cls
  13. @debugmethods
  14. class Calc:
  15.     def add(self, x, y):
  16.         return x+y
  17.     def mul(self, x, y):
  18.         return x\*y
  19.     def div(self, x, y):
  20.         return x/y
  21. mycal = Calc()
  22. print(mycal.add(2, 3))
  23. print(mycal.mul(5, 2))
复制代码
执行结果是:
  1. Full name of this method: Calc.add
  2. 5
  3. Full name of this method: Calc.mul
  4. 10
复制代码
这个方案很漂亮。但是,如果变更一下需求,例如我们希望 Calc 的所有子类的方法执行时,都先输出一下它的全限定名,该怎么办呢?
在每一个子类上加上 @debugmethods 是一种方案,但是有点啰嗦,是不是?
该基于元类的解决方案出场了,以下是个例子:
  1. from functools import wraps
  2. def debug(func):
  3.     @wraps(func)
  4.     def wrapper(*args, **kwargs):
  5.         print("Full name of this method:", func.__qualname__)
  6.         return func(*args, **kwargs)
  7.     return wrapper
  8. def debugmethods(cls):
  9.     for key, val in vars(cls).items():
  10.         if callable(val):
  11.             setattr(cls, key, debug(val))
  12.     return cls
  13. class debugMeta(type):
  14.     def __new__(cls, clsname, bases, clsdict):
  15.         obj = super().__new__(cls, clsname, bases, clsdict)
  16.         obj = debugmethods(obj)
  17.         return obj
  18. class Base(metaclass=debugMeta):
  19.     pass
  20. class Calc(Base):
  21.     def add(self, x, y):
  22.         return x+y
  23. class Calc_adv(Calc):
  24.     def mul(self, x, y):
  25.         return x\*y
  26. mycal = Calc_adv()
  27. print(mycal.mul(2, 3))
复制代码
执行结果是:
  1. Full name of this method: Calc_adv.mul
  2. 6
复制代码
何时使用元类

该说的基本说完了,剩下最好一件事。元编程算是 Python 的一个魔法,多数时候我们其实用不到。但是什么时候需要呢?大概有三种情况:

  • 如果我们想要一个特性,沿着继承层次结构向下传递,可以用;
  • 如果我们想在类创建后,能动态修改,可以用;
  • 如果我们是在开发类库或者 API,可能会用到;
  作者:袁首京  原创文章,转载时请保留此声明,并给出原文连接。

来源:https://www.cnblogs.com/rockety/p/17298553.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具