设为首页 收藏本站
查看: 829|回复: 0

[经验分享] Python中的元编程

[复制链接]

尚未签到

发表于 2018-8-6 07:20:42 | 显示全部楼层 |阅读模式
  就像元数据是关于数据的数据一样,元编程是编写程序来操作程序(Just like metadata is data about data, metaprogramming is writing programs that manipulate programs)。一个常见的看法是元编程是用来成成其他程序的程序,但是实际上它的含义更为广泛(It's a common perception that metaprograms are the programs that generate other programs. But the paradigm is even broader)。所有用于读取、分析、转换或修改自身的程序都是元编程的例子。比如:


  • Domain-specific languages (DSLs)
  • Parsers
  • Interpreters
  • Compilers
  • Theorem provers
  • Term rewriters
  这篇教程介绍Python中的元编程,它通过对Python特性的回顾来更新您的Python知识,这样您就可以更好地理解本文中的概念。本文也解释了Python中的type函数除了返回一个对象(上层的)的类之外是如何拥有更重要的意义的。然后,讨论了在Python中元编程的方法以及元编程如何简化某些特定类型的任务。

一点自我反省
  如果你已经由一些Python编程经历,你可能知道那句话:Python中一切皆对象,类创建对象。但是如果一切皆对象(则类也是对象),那么是谁创建了类呢?这正是我要回答的问题。
  我们来验证一下前面的说法是否正确
  

>>>>
...     pass  
>>> some_object = SomeClass()
  
>>> type(some_obj)
  
<__main__.SomeClass instance at 0x7f8de4432f80>
  

  可见,type()函数作用于一个对象时,返回这个对象的类(即该对象由哪个类创建)
  

>>> import inspect  
>>>inspect.isclass(SomeClass)
  
True
  
>>>inspect.isclass(some_object)
  
False
  
>>>inspect.isclass(type(some_object))
  
True
  

  inspect.isclass函数返回True如果传给它一个类,对于其他类型返回False。因为some_object不是类(它是类的一个实例),所以 inspect.isclass() 返回False。而type(some_object)返回了创建 some_object 的类,因此inspect.isclass(type(some_object))返回True:
  

>>> type(SomeClass)  
<type 'classobj'>>>>
  
inspect.isclass(type(SomeClass))
  
True
  


  classobj是一个特殊的类,在Python3中所有的类都默认继承自它。现在一切变得有道理了,但是>  

>>> type(type(SomeClass))  
<type 'type'>
  
>>>inspect.isclass(type(type(SomeClass)))
  
True
  
>>>type(type(type(SomeClass)))
  
<type 'type'>
  
>>>inspect.isclass(type(type(type(SomeClass))))
  
True
  

  有点意思是么?再来看那个关于Python的名言(一切皆对象)好像并不是那么精确,这样说可能会更好:
  Python中除了type以外一切皆对象,他们要么是类的对象,要么是元类的对象。
  来验证这个观点:
  

>>> some_obj = SomeClass()  
>>> isinstance(some_obj,SomeClass)
  
True
  
>>> isinstance(SomeClass, type)
  
True
  

  因此我们可以知道实例是一个类的实例化,而类是一个元类的实例化。

type并不是我们以为的那样
  type 本身就是一个类,并且它是他自己的 type,它是一个元类。元类可以实例化为类并且定义类的行为,就像类可以实例化为对象并且定义对象的行为一样。
  type 是 Python 中一个内建的元类,来控制Python中类的行为,我们可以通过继承自 type 来自定义一个元类。元类是Python中进行元编程的途径。

定义一个类时发生了什么
  让我们先复习一下我们已知的知识,在Python中构成代码的基本单元有:


  • Statements
  • Functions
  • Classes
  在代码中由 Statements 来完成实际的工作,Statements 可以在全局范围(module level)或是本地范围(within a function)。函数是包含一条或多条语句,用来执行特定任务的,可复用的代码单元。函数同样可以定义在全局范围或本地范围,也可以作为类的方法。类提供了“面向对象编程”的能力,类定义了对象如何被实例化以及他们实例化后将会拥有的属性和方法。
  类的命名空间存储于字典中,例如
  

>>>>
...    >  
...     def __init__(self):
  
...         self.some_var = 'Some value'
  

  
>>> SomeClass.__dict__
  
{'__doc__': None,
  '__init__': <function __main__.__init__>,
  '__module__': '__main__',
  'class_var': 1}
  

  
>>> s = SomeClass()
  

  
>>> s.__dict__
  
{'some_var': 'Some value'}
  

  下面详细介绍下当遇到class关键字时,会发生什么:


  • 类的主体(语句和函数)被隔离(The body (statements and functions) of the>
  • 类的命名空间字典被创建(但是还未向字典中添加键值对)
  • 类中的代码开始执行,然后代码中定义的所有属性和方法以及一些其他信息(如'__doc__')被添加到命名空间字典中
  • 将要被创建的这个类的元类被识别(这里是简译了,请看原句)(The metaclass is>The metaclass is then called with the name, bases, and attributes of the>
  由于 type 是Python中默认的元类,所以你可以用 type 去创建类。

type的另一面

  type(),当只跟一个参数时,产生现有类的类型信息(produces the type information of an existing>  因此
  

class SomeClass: pass  

  等价于
  

SomeClass = type('SomeClass', (), {})  

  并且
  

class ParentClass:  pass
  

  
class SomeClass(ParentClass):
  some_var = 5
  def some_function(self):
  print("Hello!")
  

  等价于
  

def some_function(self):  print("Hello")
  
ParentClass = type('ParentClass', (), {})
  
SomeClass = type('SomeClass',
  [ParentClass],
  {'some_function': some_function,
  'some_var':5})
  

  因此,通过我们自定义的元类而不是 type,我们可以给类注入一些行为(we can inject some behavior to the>
装饰器(Decorators):Python中元编程的一个常见示例
  装饰器是一种修改函数行为或者类行为的方法。装饰器的使用看起来大概是这个样子:
  

@some_decorator  
def some_func(*args, **kwargs):
  pass
  

  @some_decorator只是一种语法糖,表示函数some_func被另一个函数some_decorator封装起来。我们知道函数和类(除了 type 这个元类)在Python中都是对象,这意味着它们可以:


  • 分配给一个变量(Assigned to a variable)
  • 复制(copied)
  • 作为参数传递给另一个函数(Passed as parameters to other functions)
  上面的写法等同于
  

some_func = some_decorator(some_func)  

  你可能会想知道 some_decorator 是如何定义的
  

def some_decorator(f):  """
  The decorator receives function as a parameter.
  """
  def wrapper(*args, **kwargs):
  # doing something before calling the function
  f(*args, **kwargs)
  # doing something after the function is called
  return wrapper
  

  现在假设我们有一个从URL抓取数据的函数。被抓取服务器上有限流机制当它检测到同一个IP地址发来过多的请求并且请求间隔都一样时,会限制当前IP的请求。为了让我们的抓取程序表现的更随机一些,我们会让程序在每次请求之后暂定一小段随机时间来“欺骗”被抓取服务器。这个需求我们能通过装饰器来实现么?看代码
  

from functools import wraps  
import random
  
import time
  

  
def wait_random(min_wait=1, max_wait=5):
  def inner_function(func):
  @wraps(func)
  def wrapper(*args, **kwargs):
  time.sleep(random.randint(min_wait, max_wait))
  return func(*args, **kwargs)
  return wrapper
  return inner_function
  

  
@wait_random(10, 15)
  
def function_to_scrape():
  # some scraping stuff
  

  其中 inner_function 和 @wraps 装饰器可能对你来说还比较新。如果你仔细看,inner_function 和我们上文中定义的 some_decorator 类似。之所以用了三层def关键字,是因为装饰器wait_random要接受参数(min_wait和max_wait)。@wraps是个很好用的装饰器,他保存原函数(这里是func)的元数据(例如name, doc string, and function attributes)。如果我们没有用 @wraps,当我们对装饰之后的函数调用 help() 时 将不能得到有用的(期望的)结果,它将返回 wrapper 函数的 docstring,而不是 func 函数的(正常我们期望是func的)。
  但是如果你有一个爬虫类包含多个类似的函数呢:
  

class Scraper:  def func_to_scrape_1(self):
  # some scraping stuff
  pass
  def func_to_scrape_2(self):
  # some scraping stuff
  pass
  def func_to_scrape_3(self):
  # some scraping stuff
  pass
  

  一种方案是对每个方法前都用 @wait_random 进行装饰。但是我们可以做的更优雅:我们可以创建一个“类装饰器”。思路是遍历类的名称空间,识别出函数,然后用我们的装饰器进行封装
  

def>for name, val in vars(cls).items():
  # `callable` return `True` if the argument is callable
  # i.e. implements the `__call`
  if callable(val):
  # instead of val, wrap it with our decorator.
  setattr(cls, name, wait_random()(val))
  return cls
  

  现在我们可以用 @classwrapper 来封装整个Scraper类。但是再进一步,如果我们有很多和Scraper相似的类呢?当然你可以分别对每个类用 @classwrapper 进行装饰,但是也可以更优雅:创建一个元类。

元类(Metaclasses)
  编写一个元类包含两步:


  • 创建一个子类继承自元类 type(Write a subclass of the metaclass type)
    通过“元类钩子”将新的元类插入到类创建过程(Insert the new metaclass into the>
  我们创建 type 元类的子类,修改一些魔术方法,像__init__,__new__,__prepare__以及__call__以实现在创建类的过程中修改类的行为。这些方法包含了像父类,类名,属性等信息。Python2中,元类钩子(metaclass hook)是类中一个名为__metaclass__的静态属性(the metaclass hook is a static field in the>metaclass)。Python3中, 你可以在类的基类列表中指定元类作为元类参数(you can specify the metaclass as a metaclass argument in the base-class list of a>  

>>>>
...     def __init__(cls, name, bases, attrs):
  
...         for name, value in attrs.items():
  # do some stuff
  
...             print('{} :{}'.format(name, value))

  
>>>>  
...    >  

  
__module__ :__main__
  
__metaclass__ :<class '__main__.CustomMetaClass'>
  
class_attribute :Some string
  

  属性被自动打印出来由于 CustomMetaClass 中的 __init__方法。我们来假设一下在你的Python项目中有一位“烦人”的伙伴习惯用 camelCase(驼峰法)方式来命名类中的属性和方法。你知道这不是一条好的实践,应该用 snake_case(即下划线方式)方式。那么我们可以编写一个元类来讲所有驼峰法的属性名称和方法名称修改为下划线方式吗?
  

def camel_to_snake(name):  """
  A function that converts camelCase to snake_case.
  Referred from: https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
  """
  import re
  s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
  return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
  

  
class SnakeCaseMetaclass(type):
  def __new__(snakecase_metaclass, future_class_name,
  future_class_parents, future_class_attr):
  snakecase_attrs = {}
  for name, val in future_class_attr.items():
  snakecase_attrs[camel_to_snake(name)] = val
  return type(future_class_name, future_class_parents,
  snakecase_attrs)
  

  你可能已经注意到这里用了__new__方法而不是__init__。实际上 __new是创建一个实例过程的第一步,它负责返回由类实例化而来的实例。另一方面, \init并不返回任何东西,它仅仅负责在实例创建之后对实例进行各种初始化。记住一个简单的法则:**当你需要控制一个实例的创建过程时用`new;当你需要对一个新创建的实例进行初始化时用init__`**。
  一般在实现元类的时候不用 __init,因为他“不够强大”:在实际调用 \init之前类的创建过程已经完成。你可以理解`init`就像一个类装饰器,但不同的是 \init__在创建子类的时候会被调用,而装饰器则不会。
  由于我们的任务包含创建一个新的实例(防止这些驼峰法的属性名称潜入到类中),重写我自定义元类 SnakeCaseMetaClass 中的 __new__方法。让我们来检查一下这是否按预期工作了:
  

>>>>
...     camelCaseVar = 5
  
>>> SomeClass.camelCaseVar
  
AttributeError: type object 'SomeClass' has no attribute 'camelCaseVar'
  
>>> SomeClass.camel_case_var
  
5
  

  结果是预期的。现在你知道了Python中如何编写元类。

总结
  在这篇文章中,介绍了Python中实例,类和元类的关系。也展示了元编程的知识,这是一种操作代码的方法。我们还讨论了装饰器和类装饰器用来对类和方法(函数)注入一些额外的行为。然后我们展示了如何通过继承默认的元类type来创建自定义的元类。最后我们展示了一些用到元类的场景。关于是否使用元类,在网上也有比较大的争议。但是通过本文我们应该能分析什么类型的问题用元编程来解决可能会更好。
  由于本人能力有限,若有有不精准或模糊的地方,请见原文链接。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-547266-1-1.html 上篇帖子: Python的字典 { } 下篇帖子: Python进程、线程那点事儿
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表