iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Python探针如何完成调用库的数据提取
  • 463
分享到

Python探针如何完成调用库的数据提取

2023-07-06 12:07:54 463人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

今天小编给大家分享一下python探针如何完成调用库的数据提取的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.简单粗暴的方

今天小编给大家分享一下python探针如何完成调用库的数据提取的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

1.简单粗暴的方法--对mysql库进行封装

要统计一个执行过程, 就需要知道这个执行过程的开始位置和结束位置, 所以最简单粗暴的方法就是基于要调用的方法进行封装,在框架调用Mysql库和mysql库中间实现一个中间层, 在中间层完成耗时统计,如:

# 伪代码def my_execute(conn, sql, param): # 针对MySql库的统计封装组件 with MyTracer(conn, sql, param):     # 以下为正常使用MySql库的代码with conn.cursor as cursor: cursor.execute(sql, param)...

看样子实现起来非常不错, 而且更改非常方便, 但由于是在最顶层的api上进行修改, 其实是非常不灵活的, 同时在cursor.execute里会进行一些预操作, 如把sql和param进行拼接, 调用nextset清除当前游标的数据等等。我们最后拿到的数据如时间耗时也是不准确的, 同时也没办法得到一些详细的元数据, 如错误码等等.

如果要拿到最直接有用的数据,就只能去改源代码, 然后再调用源代码了, 但是如果每个库都需要改源代码才能统计, 那也太麻烦了, 好在Python也提供了一些类似探针的接口, 可以通过探针把库的源码进行替换完成我们的代码.

2.Python的探针

在Python中可以通过sys.meta_path来实现import hook的功能, 当执行 import 相关操作时, 会根据sys.meta_path定义的对象对import相关库进行更改.sys.meta_path中的对象需要实现一个find_module方法, 这个find_module方法返回None或一个实现了load_module方法的对象, 我们可以通过这个对象, 针对一些库在import时, 把相关的方法进行替换, 简单用法如下,通过hooktime.sleep让他在sleep的时候能打印消耗的时间.

import importlibimport sysfrom functools import wrapsdef func_wrapper(func):    """这里通过一个装饰器来达到狸猫换太子和获取数据的效果"""    @wraps(func)    def wrapper(*args, **kwargs):        # 记录开始时间        start = time.time()        result = func(*args, **kwargs)        # 统计消耗时间        end = time.time()        print(f"speed time:{end - start}")        return result    return wrapperclass MetaPathFinder:    def find_module(self, fullname, path=None):        # 执行时可以看出来在import哪些模块        print(f'find module:{path}:{fullname}')        return MetaPathLoader()class MetaPathLoader:    def load_module(self, fullname):        # import的模块都会存放在sys.modules里面, 通过判断可以减少重复import        if fullname in sys.modules:            return sys.modules[fullname]        # 防止递归调用        finder = sys.meta_path.pop(0)        # 导入 module        module = importlib.import_module(fullname)        if fullname == 'time':            # 替换函数            module.sleep = func_wrapper(module.sleep)        sys.meta_path.insert(0, finder)        return modulesys.meta_path.insert(0, MetaPathFinder())if __name__ == '__main__':    import time    time.sleep(1)# 输出示例:# find module:datetime# find module:time# load module:time# find module:math# find module:_datetime# speed time:1.00073385238647468

3.制作探针模块

了解完了主要流程后, 可以开始制作自己的探针模块了, 由于示例只涉及到aiomysql模块, 那么在MetaPathFinder.find_module中需要只对aiomysql模块进行处理, 其他的先忽略. 然后我们需要确定我们要把aiomysql的哪个功能给替换, 从业务上来说, 一般情况下我们只要cursor.execute, cursor.fetchone, cursor.fetchall, cursor.executemany这几个主要的操作,所以需要深入cursor看看如何去更改代码, 后者重载哪个函数.

先cursor.execute的源码(cursor.executemanay也类似), 发现会先调用self.nextset的方法, 把上个请求的数据先拿完, 再合并sql语句, 最后通过self._query进行查询:

async def execute(self, query, args=None):    """Executes the given operation    Executes the given operation substituting any markers with    the given parameters.    For example, getting all rows where id is 5:        cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))    :param query: ``str`` sql statement    :param args: ``tuple`` or ``list`` of arguments for sql query    :returns: ``int``, number of rows that has been produced of affected    """    conn = self._get_db()    while (await self.nextset()):        pass    if args is not None:        query = query % self._escape_args(args, conn)    await self._query(query)    self._executed = query    if self._echo:        logger.info(query)        logger.info("%r", args)    return self._rowcount

再看cursor.fetchone的源码(cursor.fetchall也类似), 发现其实是从缓存中获取数据,

这些数据在执行cursor.execute中就已经获取了:

def fetchone(self):    """Fetch the next row """    self._check_executed()    fut = self._loop.create_future()    if self._rows is None or self._rownumber >= len(self._rows):        fut.set_result(None)        return fut    result = self._rows[self._rownumber]    self._rownumber += 1    fut = self._loop.create_future()    fut.set_result(result)    return fut

综合上面的分析, 我们只要对核心的方法self._query进行重载即可拿到我们要的数据, 从源码中我们可以知道, 我们能获取到传入self._query的self和sql参数, 根据self又能获取到查询的结果, 同时我们通过装饰器能获取到运行的时间, 要的数据基本都到齐了,

按照思路修改后的代码如下:

import importlibimport timeimport sysfrom functools import wrapsfrom typing import cast, Any, Callable, Optional, Tuple, TYPE_CHECKINGfrom types import ModuleTypeif TYPE_CHECKING:    import aiomysqldef func_wrapper(func: Callable):    @wraps(func)    async def wrapper(*args, **kwargs) -> Any:        start: float = time.time()        func_result: Any = await func(*args, **kwargs)        end: float = time.time()        # 根据_query可以知道, 第一格参数是self, 第二个参数是sql        self: aiomysql.Cursor = args[0]        sql: str = args[1]        # 通过self,我们可以拿到其他的数据        db: str = self._connection.db        user: str = self._connection.user        host: str = self._connection.host        port: str = self._connection.port        execute_result: Tuple[Tuple] = self._rows        # 可以根据自己定义的agent把数据发送到指定的平台, 然后我们就可以在平台上看到对应的数据或进行监控了,         # 这里只是打印一部分数据出来        print({            "sql": sql,            "db": db,            "user": user,            "host": host,            "port": port,            "result": execute_result,            "speed time": end - start        })        return func_result    return cast(Callable, wrapper)class MetaPathFinder:    @staticmethod    def find_module(fullname: str, path: Optional[str] = None) -> Optional["MetaPathLoader"]:        if fullname == 'aiomysql':            # 只有aiomysql才进行hook            return MetaPathLoader()        else:            return Noneclass MetaPathLoader:    @staticmethod    def load_module(fullname: str):        if fullname in sys.modules:            return sys.modules[fullname]        # 防止递归调用        finder: "MetaPathFinder" = sys.meta_path.pop(0)        # 导入 module        module: ModuleType = importlib.import_module(fullname)        # 针对_query进行hook        module.Cursor._query = func_wrapper(module.Cursor._query)        sys.meta_path.insert(0, finder)        return moduleasync def test_mysql() -> None:    import aiomysql    pool: aiomysql.Pool = await aiomysql.create_pool(        host='127.0.0.1', port=3306, user='root', passWord='123123', db='mysql'    )    async with pool.acquire() as conn:        async with conn.cursor() as cur:            await cur.execute("SELECT 42;")            (r,) = await cur.fetchone()            assert r == 42    pool.close()    await pool.wait_closed()if __name__ == '__main__':    sys.meta_path.insert(0, MetaPathFinder())    import asyncio    asyncio.run(test_mysql())# 输出示例:# 可以看出sql语句与我们输入的一样, db, user, host, port等参数也是, 还能知道执行的结果和运行时间# {'sql': 'SELECT 42;', 'db': 'mysql', 'user': 'root', 'host': '127.0.0.1', 'port': 3306, 'result': ((42,),), 'speed time': 0.00045609474182128906}

这个例子看来很不错, 但是需要在调用的入口处显式调用该逻辑, 通常一个项目可能有几个入口, 每个入口都显示调用该逻辑会非常麻烦, 而且必须先调用我们的hook逻辑后才能import, 这样就得订好引入规范, 不然就可能出现部分地方hook不成功, 如果能把引入hook这个逻辑安排在解析器启动后马上执行, 就可以完美地解决这个问题了. 查阅了一翻资料后发现,python解释器初始化的时候会自动import PYTHONPATH下存在的sitecustomize和usercustomize模块, 我们只要创建该模块, 并在模块里面写入我们的 替换函数即可。

.├── __init__.py├── hook_aiomysql.py├── sitecustomize.py└── test_auto_hook.py

hook_aiomysql.py是我们制作探针的代码为例子, 而sitecustomize.py存放的代码如下, 非常简单, 就是引入我们的探针代码, 并插入到sys.meta_path:

import sysfrom hook_aiomysql import MetaPathFindersys.meta_path.insert(0, MetaPathFinder())

test_auto_hook.py则是测试代码:

import asynciofrom hook_aiomysql import test_mysqlasyncio.run(test_mysql())

接下来只要设置PYTHONPATH并运行我们的代码即可(如果是项目的话一般交由superisor启动,则可以在配置文件中设置好PYTHONPATH):

(.venv) ➜  python_hook git:(master) ✗ export PYTHONPATH=.      (.venv) ➜  python_hook git:(master) ✗ python test_auto_hook.py {'sql': 'SELECT 42;', 'db': 'mysql', 'user': 'root', 'host': '127.0.0.1', 'port': 3306, 'result': ((42,),), 'speed time': 0.000213623046875}

4.直接替换方法

可以看到上面的方法很好的运行了, 而且可以很方便的嵌入到我们的项目中, 但是依赖与sitecustomize.py文件很难让他抽离成一个第三方的库, 如果要抽离成第三方的库就得考虑看看有没有其他的方法。在上面介绍MetaPathLoader时说到了sys.module, 在里面通过sys.modules来减少重复引入:

class MetaPathLoader:    def load_module(self, fullname):        # import的模块都会存放在sys.modules里面, 通过判断可以减少重复import        if fullname in sys.modules:            return sys.modules[fullname]        # 防止递归调用        finder = sys.meta_path.pop(0)        # 导入 module        module = importlib.import_module(fullname)        if fullname == 'time':            # 替换函数            module.sleep = func_wrapper(module.sleep)        sys.meta_path.insert(0, finder)        return module

这个减少重复引入的原理是, 每次引入一个模块后, 他就会存放在sys.modules, 如果是重复引入, 就会直接刷新成最新引入的模块。上面之所以会考虑到减少重复import是因为我们不会在程序运行时升级第三方库的依赖。利用到我们可以不考虑重复引入同名不同实现的模块, 以及sys.modules会缓存引入模块的特点, 我们可以把上面的逻辑简化成引入模块->替换当前模块方法为我们修改的hook方法。

import timefrom functools import wrapsfrom typing import Any, Callable, Tuple, castimport aiomysqldef func_wrapper(func: Callable):    """和上面一样的封装函数, 这里简单略过"""# 判断是否hook过_IS_HOOK: bool = False# 存放原来的_query_query: Callable = aiomysql.Cursor._query# hook函数def install_hook() -> None:    _IS_HOOK = False    if _IS_HOOK:        return    aiomysql.Cursor._query = func_wrapper(aiomysql.Cursor._query)    _IS_HOOK = True# 还原到原来的函数方法def reset_hook() -> None:    aiomysql.Cursor._query = _query    _IS_HOOK = False

代码简单明了,接下来跑一跑刚才的测试:

import asyncioimport aiomysqlfrom demo import install_hook, reset_hookasync def test_mysql() -> None:    pool: aiomysql.Pool = await aiomysql.create_pool(        host='127.0.0.1', port=3306, user='root', password='', db='mysql'    )    async with pool.acquire() as conn:        async with conn.cursor() as cur:            await cur.execute("SELECT 42;")            (r,) = await cur.fetchone()            assert r == 42    pool.close()    await pool.wait_closed()print("install hook")install_hook()asyncio.run(test_mysql())print("reset hook")reset_hook()asyncio.run(test_mysql())print("end")

通过测试输出可以发现我们的逻辑的正确的, install hook后能出现我们提取的元信息, 而reset后则不会打印原信息

install hook{'sql': 'SELECT 42;', 'db': 'mysql', 'user': 'root', 'host': '127.0.0.1', 'port': 3306, 'result': ((42,),), 'speed time': 0.000347137451171875}reset hookend

以上就是“Python探针如何完成调用库的数据提取”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网Python频道。

--结束END--

本文标题: Python探针如何完成调用库的数据提取

本文链接: https://www.lsjlt.com/news/358182.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Python探针如何完成调用库的数据提取
    今天小编给大家分享一下Python探针如何完成调用库的数据提取的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.简单粗暴的方...
    99+
    2023-07-06
  • Python探针完成调用库的数据提取
    目录1.简单粗暴的方法--对mysql库进行封装2.Python的探针3.制作探针模块4.直接替换方法5.总结1.简单粗暴的方法--对mysql库进行封装 要统计一个执行过程, 就需...
    99+
    2024-04-02
  • Python探针怎么完成调用库的数据提取
    今天小编给大家分享一下Python探针怎么完成调用库的数据提取的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.简单粗暴的方...
    99+
    2023-06-30
  • Python如何提取chm数据
    目录Python提取chm数据需求场景查用python自带的.chm官方文档总结Python提取chm数据 需求场景 chm格式文档中的内容,提取保存为html 方法一 使用在线转换...
    99+
    2023-01-06
    Python提取chm数据 Python chm数据 Py提取chm数据
  • python如何提取数据中的部分数据
    您可以使用Python中的切片(slicing)来提取数据中的部分数据。切片允许您根据索引从序列(如列表、字符串或元组)中选择部分子...
    99+
    2023-08-23
    python
  • Vue在页面数据渲染完成之后如何调用
    这篇文章主要为大家展示了“Vue在页面数据渲染完成之后如何调用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Vue在页面数据渲染完成之后如何调用”这篇文章吧。在...
    99+
    2024-04-02
  • 如何调取阿里云数据库数据信息
    本文主要介绍如何调取阿里云数据库数据信息,包括了阿里云数据库的基本介绍、如何创建数据库连接、如何查询数据以及如何处理数据。 一、阿里云数据库的基本介绍阿里云数据库是阿里云提供的一种云计算服务,可以帮助用户快速、安全地存储和管理数据。它提供了...
    99+
    2023-11-18
    阿里 数据库 数据
  • python如何调用api接口获取数据
    在Python中,可以使用`requests`库来调用API接口获取数据。下面是一个简单的例子:```pythonimport re...
    99+
    2023-08-25
    python
  • Python数据获取如何实现图片数据提取
    本篇内容主要讲解“Python数据获取如何实现图片数据提取”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python数据获取如何实现图片数据提取”吧!有很多功能…比如用户画...
    99+
    2023-06-30
  • 如何在oracle数据库中提取重要数据
    要在Oracle数据库中提取重要数据,您可以使用SQL查询语句和Oracle数据库的相关功能。 以下是一些在Oracle数据库中提取...
    99+
    2024-04-09
    oracle oracle数据库
  • java如何调用数据库里的数据
    在Java中调用数据库的数据,需要使用JDBC(Java Database Connectivity)来连接数据库并执行相应的操作。...
    99+
    2023-09-11
    java 数据库
  • vue2.0$nextTick如何监听数据渲染完成之后的回调函数
    这篇文章主要介绍vue2.0$nextTick如何监听数据渲染完成之后的回调函数,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!vue里面本身带有两个回调函数:一个是`Vue.next...
    99+
    2024-04-02
  • 数据库存版本迁移:如何轻松完成?
    1. 选择合适的数据库迁移工具 数据库迁移工具可以帮助您轻松完成数据库版本迁移。市面上有很多数据库迁移工具,您可以根据自己的需要选择合适的工具。一些常用的数据库迁移工具包括: Flyway Liquibase DbSchema Sch...
    99+
    2024-02-27
    数据库版本迁移、数据库迁移工具、数据库备份、数据库还原
  • Python如何从PDF中提取元数据
    这篇文章主要讲解了“Python如何从PDF中提取元数据”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python如何从PDF中提取元数据”吧!PyPdf PyPDF2 PyPDF4的历史最...
    99+
    2023-06-02
  • python如何调用pymssql包操作SqlServer数据库
    本篇内容介绍了“python如何调用pymssql包操作SqlServer数据库”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!pymssql...
    99+
    2023-07-02
  • 如何使用python wasmtime调用rust生成的wasm库
    目录安装rust target wasm32-wasi编写rust库将rust库编译为wasm字节码安装python wasmtime库参考链接本文介绍了使用python wasmt...
    99+
    2023-01-04
    python wasmtime调用rust生成的wasm库 python wasmtime调用wasm库
  • python如何提取指定行和列的数据
    在Python中,你可以使用numpy库来提取指定行和列的数据。假设你有一个2维数组data,你想要提取第2行和第3列的数据,可以使...
    99+
    2023-08-23
    python
  • 使用python如何提取JSON数据指定内容
    目录如何提取JSON数据指定内容假设我们要获取'pic_str'里的数据1、JSON数据为字符串类型2、JSON数据为字典类型如何提取复杂JSON的数据 例...
    99+
    2024-04-02
  • python如何调用dll库中的函数
    在python中使用ctypes模块调用dll库中函数的方法首先,在Python项目中引入ctypes模块;from ctypes import *ctypes模块引入后,在Python中加载dll库;Objdll = ctypes.Win...
    99+
    2024-04-02
  • MyBatis如何获取数据库自生成的主键Id
    这篇文章将为大家详细讲解有关MyBatis如何获取数据库自生成的主键Id,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。MyBatis获取数据...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作