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

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

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

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

感谢创作者的好文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/335180.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

服务器系统js文件报错,js服务器文件

js服务器文件 内容精选换一换登录Windows操作系统的弹性云服务器时,需使用密码方式登录。因此,用户需先根据创建弹性云服务器时使用的密钥文件,获取该弹性云服务器初始安装时系统生成的管理员密码(Administrator帐户或Cloudbase-init设置的帐…

云盘存储 教学反思_wps轻松办公优秀教学设计范文

wps轻松办公优秀教学设计范文[教学目的与要求]1.学会登录“WPS轻办公”2.学会保存、打开“云文档”3.学会把文档以图片的方式分享到“微博”4.通过观察“WPS轻办公”,了解“WPS轻办公”,初步会用“我的圈子”功能。[教…

Adobe PhotoShop(PS) for Mac 快捷键/PS快捷键

文章目录综合类图层视图工具箱综合类 功能说明快捷键打开文件Command O创建文档Command N存储为Web格式Command Option Shift S自由变换Command T切换到最初状态Option Command Z重做Shift Command Z自定义快捷键Option Command Shift K首选项Command K色阶Comman…

spring启动执行_执行器的Spring启动和安全性事件

spring启动执行Spring Boot Actuator提供了审核功能,用于在启用了Spring Security的Spring Boot应用程序中发布和侦听与安全相关的事件。 默认事件是身份验证成功,身份验证失败和访问被拒绝,但是可以使用自定义事件进行扩展。 确保在项目中启…

系统运维包括哪些内容_智能养老系统包括哪些?养老管理系统内容详解

智慧养老成为养老产业新的发展热点,受到了社会各界的关注。智能养老系统利用先进的IT技术手段,开发面向居家养老、社区养老、机构养老的养老系统平台,提供实时、快捷、高效、物联化、智能化的养老服务,并将医疗服务、运营商、服务…

买了服务器之后如何操作系统,买了服务器之后如何操作系统

买了服务器之后如何操作系统 内容精选换一换如果您需要使用毕昇编译器,则需要先在服务端安装毕昇编译器。毕昇编译器基于开源LLVM开发,并进行了优化和改进,同时将flang作为默认的Fortran语言前端编译器,是针对鲲鹏平台的高性能编译…

敏捷中gwt含义_在GWT中序列化/反序列化Json

敏捷中gwt含义JSON和GWT 最近, GWT用户小组中进行了有趣的讨论 ,涉及在客户端对JSON进行序列化/反序列化的最佳实践。 这篇文章旨在突出其重点。 到目前为止,在GWT中有三种将对象转换为JSON并从客户端转换回JSON的方法: gwt-jack…

图片高亮处理编程_GMT语法高亮-智能提示-代码补全插件

GMT(Generic Mappint Tools)是地学界应用非常广泛的一款绘图兼数据处理的开源软件。其开发团队也是非常活跃,此软件还在不断的发展和更新中,变得越来越强大。目前已经有164个模块,而每一个模块又有很多命令参数。因为gmt是命令行软件&#xf…

超时空机战服务器配置信息错误,超时空机战熔炉篇FAQ教你如何合理的使用熔炉...

在超时空机战这款游戏中,有一处是叫做熔炉的地方,在其中可以分解物品得到其他的物品,可是怎么才能更好的利用熔炉分解物品达到资源合理利用的地步呢,接下来就和小编一起分析一下吧。1.什么是熔炉,熔炉在哪里可以找到?…

ASCII编码/Unicode编码

码位与字符的对照关系表(编码字符集) ASCII 码表,American Standard Code for Information Interchange,美国信息交换标准代码 Unicode 码表,万国码,几乎全球国家的字符都可以表示,也是用来表示…

tdd 单元测试_何时给定在单元测试和TDD中的重要性

tdd 单元测试最近,我一直在写与自动测试有关的更高级的概念(主要与Spock有关)。 但是,在进行测试培训时,我清楚地看到,通常对特定工具的知识并不是主要问题。 即使使用Spock,也可以编写肿且难以…

java 设计模式_Java设计模式的常见应用场景

一、Java I/O中的设计模式1、适配器模式适配器模式就是把一个类的接口变换成客户端所能接受的另一种接口,从而使两个接口不匹配而无法在一起工作的两个类能够在一起工作。通常被用在一个项目需要引用一些开源框架来一起工作时,这些框架的内部都有一些关于…

abaqus实例_使用Python在ABAQUS中创建XYData数据

这篇的正题是要讲一个愚蠢的小故事,无奈之举,恐成笑柄。但是从PythonAbaqus前后处理的角度讲,还是有点意义的,所以才敢拿出来献丑。三年航天器型号研制工作干下来,积累了不少陋习,但内心还是想努力克服的。…

常量和字面量的区别

字面量 是指由字母,数字等构成的字符串或者数值,它只能作为右值出现,所谓右值是指等号右边的值,如:int a123这里的a为左值,123为右值。 常量 常量和变量都属于变量,只不过常量是赋过值后不能…

java学习笔记_Java学习笔记day11

Map集合java.util.Map<k,v>集合 Map集合特点&#xff1a; 1.Map集合是一个双列集合&#xff0c;一个元素包含两个值(一个key&#xff0c;一个value) 2.Map集合中的元素&#xff0c;key和value的数据类型可以相同&#xff0c;也可以不同 3.Map集合中掉元素&#xff0c;key…

slice_Spring Boot Web Slice测试–示例

sliceSpring Boot 引入了一段时间的测试切片 &#xff0c;花了我一些时间来研究它并探索它的一些细微差别。 背景 使用此功能的主要原因是减少样板。 考虑一个看起来像这样的控制器&#xff0c;仅适用于使用Kotlin编写的各种控制器。 RestController RequestMapping("/u…

几点减几点怎么列算式_[股票一般几点可以买入]股票缩量怎么买 | 爱淘股吧

股票一般买入绝技——缩量调整突破买入股票缩量怎么买下面就来详细说说几个买入绝技&#xff1a;第一种&#xff1a;伴随着成交量的缩减。(2)在缩量调整到尾声之后&#xff0c;突然出现一根放量大阳线。(3)大阳线的出现伴随着成交量的放大。其下一个交易日如果股价继续放量上攻…

Java关键字和标识符

public class HelloWorld { // public、class 是关键字&#xff0c;HelloWorld是标识符public static void main(String[] args) { // public、static、void是关键字&#xff0c;main、String、args是标识符System.out.println("Hello,World!"); // System、out、pri…

python可视化水平双向箭头_python数据可视化第二弹

代码模板&#xff1a;最前面importpandas as pdpd.plotting.register_matplotlib_converters()importmatplotlib.pyplot as plt%matplotlib inlineimportseaborn as snsprint("Setup Complete")View Code一、折线图Line chartsplt.figure(figsize(14,6))#Add titlepl…

扩展 junit 框架_JUnit 5扩展模型的生命周期

扩展 junit 框架JUnit5最终版本即将来临 &#xff08;当前是M4&#xff09;&#xff0c;我已经开始尝试如何编写扩展了。 在JUnit5中 &#xff0c;您没有使用Runners &#xff0c; Rules &#xff0c; ClassRules等&#xff0c;而是只有一个Extension API来实现自己的扩展。 …