△点击上方“Python猫”关注 ,回复“

1

”领取电子书

剧照 | 《棋魂》

原文:https://segmentfault.com/a/1190000004889212

大家好,我是猫哥。

关于 Python 中探针的运用,我之前写过一篇《由浅入深:Python 中如何实现自动导入缺失的库?》,最近看到一篇文章专门写这个内容,特分享一下~~

本文呢,将简单讲述一下 Python 探针的实现原理。同时为了验证这个原理,我们也会一起来实现一个简单的统计指定函数执行时间的探针程序。

探针的实现主要涉及以下几个知识点:

sys.meta_path

sitecustomize.py

sys.meta_path

sys.meta_path 这个简单的来说就是可以实现 import hook 的功能, 当执行 import 相关的操作时,会触发 sys.meta_path 列表中定义的对象。关于 sys.meta_path 更详细的资料请查阅 python 文档中 sys.meta_path 相关内容以及 PEP 0302 。

sys.meta_path 中的对象需要实现一个 find_module 方法, 这个 find_module 方法返回 None 或一个实现了 load_module 方法的对象 (代码可以从 github 上下载 part1_) :

import sys

class MetaPathFinder:

def find_module(self, fullname, path=None):

print('find_module {}'.format(fullname))

return MetaPathLoader()

class MetaPathLoader:

def load_module(self, fullname):

print('load_module {}'.format(fullname))

sys.modules[fullname] = sys

return sys

sys.meta_path.insert(0, MetaPathFinder())

if __name__ == '__main__':

import http

print(http)

print(http.version_info)

load_module 方法返回一个 module 对象,这个对象就是 import 的 module 对象了。比如我上面那样就把 http 替换为 sys 这个 module 了。

$python meta_path1.py

find_module http

load_module http

sys.version_info(major=3, minor=5, micro=1, releaselevel='final', serial=0)

通过 sys.meta_path 我们就可以实现 import hook 的功能:当 import 预定的 module 时,对这个 module 里的对象来个狸猫换太子, 从而实现获取函数或方法的执行时间等探测信息。

上面说到了狸猫换太子,那么怎么对一个对象进行狸猫换太子的操作呢?对于函数对象,我们可以使用装饰器的方式来替换函数对象(代码可以从 github 上下载 part2) :

import functools

import time

def func_wrapper(func):

@functools.wraps(func)

def wrapper(*args, **kwargs):

print('start func')

start = time.time()

result = func(*args, **kwargs)

end = time.time()

print('spent {}s'.format(end - start))

return result

return wrapper

def sleep(n):

time.sleep(n)

return n

if __name__ == '__main__':

func = func_wrapper(sleep)

print(func(3))

执行结果:

$python func_wrapper.py

start func

spent 3.004966974258423s

3

下面我们来实现一个计算指定模块的指定函数的执行时间的功能(代码可以从 github 上下载 part3) 。

假设我们的模块文件是 hello.py:

import time

def sleep(n):

time.sleep(n)

return n

我们的 import hook 是 hook.py:

import functools

import importlib

import sys

import time

_hook_modules = {'hello'}

class MetaPathFinder:

def find_module(self, fullname, path=None):

print('find_module {}'.format(fullname))

if fullname in _hook_modules:

return MetaPathLoader()

class MetaPathLoader:

def load_module(self, fullname):

print('load_module {}'.format(fullname))

# ``sys.modules`` 中保存的是已经导入过的 module

if fullname in sys.modules:

return sys.modules[fullname]

# 先从 sys.meta_path 中删除自定义的 finder

# 防止下面执行 import_module 的时候再次触发此 finder

# 从而出现递归调用的问题

finder = sys.meta_path.pop(0)

# 导入 module

module = importlib.import_module(fullname)

module_hook(fullname, module)

sys.meta_path.insert(0, finder)

return module

sys.meta_path.insert(0, MetaPathFinder())

def module_hook(fullname, module):

if fullname == 'hello':

module.sleep = func_wrapper(module.sleep)

def func_wrapper(func):

@functools.wraps(func)

def wrapper(*args, **kwargs):

print('start func')

start = time.time()

result = func(*args, **kwargs)

end = time.time()

print('spent {}s'.format(end - start))

return result

return wrapper

测试代码:

>>>import hook

>>>import hello

find_module hello

load_module hello

>>>

>>>hello.sleep(3)

start func

spent 3.0029919147491455s

3

>>>

其实上面的代码已经实现了探针的基本功能。不过有一个问题就是上面的代码需要显示的 执行 import hook 操作才会注册上我们定义的 hook。

那么有没有办法在启动 python 解释器的时候自动执行 import hook 的操作呢?答案就是可以通过定义 sitecustomize.py 的方式来实现这个功能。

sitecustomize.py

简单的说就是,python 解释器初始化的时候会自动 import PYTHONPATH 下存在的 sitecustomize 和 usercustomize 模块:

实验项目的目录结构如下(代码可以从 github 上下载 part4) :

$tree

.

├── sitecustomize.py

└── usercustomize.py

sitecustomize.py:

$cat sitecustomize.py

print('this is sitecustomize')

usercustomize.py:

$cat usercustomize.py

print('this is usercustomize')

把当前目录加到 PYTHONPATH 中,然后看看效果:

$export PYTHONPATH=.

$python

this is sitecustomize

this is usercustomize

Python 3.5.1 (default, Dec 24 2015, 17:20:27)

[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

>>>

可以看到确实自动导入了。所以我们可以把之前的探测程序改为支持自动执行 import hook (代码可以从 github 上下载 part5) 。

目录结构:

$tree

.

├── hello.py

├── hook.py

├── sitecustomize.py

sitecustomize.py:

$cat sitecustomize.py

import hook

结果:

$export PYTHONPATH=.

$python

find_module usercustomize

Python 3.5.1 (default, Dec 24 2015, 17:20:27)

[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

find_module readline

find_module atexit

find_module rlcompleter

>>>

>>> import hello

find_module hello

load_module hello

>>>

>>> hello.sleep(3)

start func

spent 3.005002021789551s

3

不过上面的探测程序其实还有一个问题,那就是需要手动修改 PYTHONPATH 。用过探针程序的朋友应该会记得, 使用 newrelic 之类的探针只需要执行一条命令就 可以了:newrelic-admin run-program python hello.py 实际上修改 PYTHONPATH 的操作是在newrelic-admin 这个程序里完成的。

下面我们也要来实现一个类似的命令行程序,就叫 agent.py 吧。

agent

还是在上一个程序的基础上修改。先调整一个目录结构,把 hook 操作放到一个单独的目录下, 方便设置 PYTHONPATH 后不会有其他的干扰(代码可以从 github 上下载 part6 )。

$mkdir bootstrap

$mv hook.py bootstrap/_hook.py

$touch bootstrap/__init__.py

$touch agent.py

$tree

.

├── bootstrap

│ ├── __init__.py

│ ├── _hook.py

│ └── sitecustomize.py

├── hello.py

├── test.py

├── agent.py

bootstrap/sitecustomize.py 的内容修改为:

$cat bootstrap/sitecustomize.py

import _hook

agent.py 的内容如下:

import os

import sys

current_dir = os.path.dirname(os.path.realpath(__file__))

boot_dir = os.path.join(current_dir, 'bootstrap')

def main():

args = sys.argv[1:]

os.environ['PYTHONPATH'] = boot_dir

# 执行后面的 python 程序命令

# sys.executable 是 python 解释器程序的绝对路径 ``which python``

# >>> sys.executable

# '/usr/local/var/pyenv/versions/3.5.1/bin/python3.5'

os.execl(sys.executable, sys.executable, *args)

if __name__ == '__main__':

main()

test.py 的内容为:

$cat test.py

import sys

import hello

print(sys.argv)

print(hello.sleep(3))

使用方法:

$python agent.py test.py arg1 arg2

find_module usercustomize

find_module hello

load_module hello

['test.py', 'arg1', 'arg2']

start func

spent 3.005035161972046s

3

至此,我们就实现了一个简单的 python 探针程序。当然,跟实际使用的探针程序相比肯定是有 很大的差距的,这篇文章主要是讲解一下探针背后的实现原理。

文中的代码:https://github.com/mozillazg/apm-python-agent-principle

Python猫技术交流群开放啦!群里既有国内一二线大厂在职员工,也有国内外高校在读学生,既有十多年码龄的编程老鸟,也有中小学刚刚入门的新人,学习氛围良好!想入群的同学,请在公号内回复『

交流群』,获取猫哥的微信

(谢绝广告党,非诚勿扰!)~

感谢创作者的好文

python find函数实现原理_非常干货:Python 探针实现原理相关推荐

  1. python用函数绘制椭圆_如何用Python画一只肥肥的柯基狗狗—turtle库绘制椭圆与弧线实践...

    历时4天,利用工作之余的细碎时间, 修修改改,终于把这只丑萌的小鼓脸柯基画了出来,我也有狗啦~code的过程多坎坷,完成时就有多快乐!成果如下: 初学turtle时所画的这只柯基,由于对turtle中 ...

  2. python画函数图像网格_如何基于Python Matplotlib实现网格动画

    -1- 如果你对本文的代码感兴趣,可以去 Github (文末提供)里查看.第一次运行的时候会报一个错误(还没找到解决办法),不过只要再运行一次就正常了. 这篇文章虽然不是篇典型的数据科学类文章,不过 ...

  3. python量化需要什么基础_【干货|python量化的基础知识,你是否真的了解】- 环球网校...

    [摘要]当今世界充满了各种数据,而python是其中一种的重要组成部分.然而,若想其有所应用,我们需要对这些python理论进行实践.其中包含很多有趣的的过程,然后将其用于某些方面.其中一种应用就是p ...

  4. python 回调函数的使用_如何在python中使用回调函数?

    我想知道如何正确使用 Python 2.7回调函数. 我在我的代码中有一些来自Cherrypy auth示例的回调函数. (这些回调会返回一个可以评估为True或False的函数,具体取决于登录的用户 ...

  5. python给函数添加属性_如何在python中自动向类添加属性?

    我有一个具有很多属性的类,这些属性在instanciating(init)时提供. 看起来像这样,但还有大约30个attr:class SomeObject: def __init__(self, f ...

  6. python class函数报错_如何掌握python中class函数用法?

    虽然大家都知道class是什么,也都知道自己在写代码时候怎么去使用这个功能,但是如果说,想让大家利用class去写一个help大家知道吗?作为一个代码老司机,不得不跟大家说明,好的代码编写一定是靠基础 ...

  7. python sort函数返回值_如何使用python sort函数?

    不知道大家在做项目时候,有没有遇到这个函数,记得小编第一次看到这个函数,一直纳闷这个函数的使用方法,而后查询了下,今日,小编再一次遇到这个函数,于是,就做了一番整理,内容请看下文. 与sort()函数 ...

  8. python画函数图像网格_使用opencv python在图像上绘制网格线

    这是我的问题解决方案.利用它.import matplotlib.pyplot as plt import matplotlib.ticker as plticker try: from PIL im ...

  9. python博客访问量_史诗级干货-python爬虫之增加CSDN访问量

    AI 人工智能 史诗级干货-python爬虫之增加CSDN访问量 史诗级干货-python爬虫之增加CSDN访问量 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法. ...

  10. python中函数和方法的区别?Python编程判断当前获取的对象是函数还是方法

    python中函数和方法的区别?Python编程判断当前获取的对象是函数还是方法 目录

最新文章

  1. 典型问题分析(十五)
  2. 使用 邮箱配置 激活码 用于 用户注册激活
  3. SpringMVC学习(四)——Spring使用到的设计模式分析
  4. Java生鲜电商平台-团购模块设计与架构
  5. Redis集群搭建使用
  6. Struts2 访问上下问对象
  7. 黑桃k游戏java实战_Java入门第三季项目实战——扑克游戏
  8. UnityHub下载缓存位置
  9. 完美解决VS2003.Net fatal error LNK1201: 写入程序数据库“.pdb”时出错我的开发环境是Win7旗舰64位+VS2003.Net,经常卡pdb错误,文末给出一个完美的解决
  10. PADS——原理图的绘制
  11. Synergy Mac和Win键盘映射
  12. Linux系统进程优先级——计算方式
  13. Python——实例1:温度转换(Python基本语法元素及框架,代码实现)
  14. https网站打不开如何解决
  15. L9110S电机驱动——让小车动起来
  16. 【蓝桥杯13】——PCF8591的应用(AD转换)
  17. (ICLR-2019)DARTS:可微分架构搜索
  18. Win10:Windows找不到文件‘Gpedit.msc‘。请确定文件名是否正确后,再试一次。
  19. 手机哔哩哔哩如何转换html5,手机b站(哔哩哔哩)账号up主头像图片如何设置?怎么保存到相册里面...
  20. 【SAP Abap】关于SAP汇率表 TCURR 的CDS开发实例

热门文章

  1. 解耦,未解耦的区别_幂等与时间解耦之旅
  2. 存储过程 锁定并发_Java并发教程–锁定:显式锁定
  3. Java的Kafka:构建安全,可扩展的消息传递应用程序
  4. TellDontAsk的扩展
  5. structure101_使用structure101分析软件包的依赖关系
  6. Java生产监控的阴暗面
  7. 为什么SpringBoot如此受欢迎,以及如何有效地学习SpringBoot?
  8. 当心Spring缓慢的事务回调
  9. java docx文档解析_带有docx4j的Java Word(.docx)文档
  10. 集成CDI和WebSockets