使用 Python v3.x 和 C API 解释 Python v2.5 代码(关于整数除法)

Posted

技术标签:

【中文标题】使用 Python v3.x 和 C API 解释 Python v2.5 代码(关于整数除法)【英文标题】:Interpret Python v2.5 code using Python v3.x and C API (with respect to integer division) 【发布时间】:2018-12-22 05:54:06 【问题描述】:

我有一个我无法控制的 v2.5 Python 代码,因为它是从支持 Python v2.5 的第三方软件导出的。

我的机器上有 Python v3.3,我想以某种方式模拟 v2.5 使用 C API。我主要关心的是 v2.x 和 v3.x 之间的整数除法。

例如我有下面的代码:

a=1
b=a/2
c=a/2.

我希望以某种方式将其解释为(使用 v3.x):

a=1
b=a//2
c=a/2.

我能做点什么吗?有没有办法像我有 Python v2.5 一样解释代码?我想 2to3 脚本不适用于我的情况,六个模块也不适用。

我还发现这个问题与我的相关: Python 2 and Python 3 dual development

谢谢

【问题讨论】:

你的意思是a//2不起作用? Python3-x explicitly 将其定义为“整数除法”。 @usr2564301 不,他是说他希望他的 Python3 解释器在 Python2 上下文中解析 b = a/2,也就是说:b = a//2 将第三方库移植到 python 3。python 2.5 和 python 3.3 在几年前就已经停止支持。 或者只是将 Python 2.5 代码作为 Python 2.5 中的子进程运行。 “我的机器上有 Python v3.3” 那就去弄 2.5。包管理器conda 非常适合treating python itself as a package,因此您可以为不同的版本创建不同的环境。 【参考方案1】:

这听起来是个坏主意——将 Python 2.5 代码解释为 Python 3 时会遇到更严重的问题,比如每个 except 语句都是语法错误,字符串类型错误(或者,如果你解决这个问题,s[i] 返回一个 int 而不是一个字节),等等。


这里要做的显而易见的事情是将代码移植到仍然受支持的 Python。

如果由于某种原因确实无法做到这一点,最简单的做法可能是围绕您需要运行的代码编写一个简单的 Python 2.5 包装器,该包装器通过sys.argv 和/或sys.stdin 获取输入并返回通过sys.exit 和/或sys.stdout 获得结果。

那么,你可以这样称呼它:

p = subprocess.run(['python2.5', 'mywrapper.py', *args], capture_output=True)
if p.retcode:
    raise Exception(p.stderr.decode('ascii'))
results = p.stdout.splitlines().decode('ascii')

但如果你真的想这样做,而这确实是你唯一的问题……这仍然不是方法

您必须低于 C API 的级别,进入像 struct PyFloat_Type 这样的内部类型对象,访问它们的 tp_as_number 结构,并将它们的 nb_floordiv 函数复制到它们的 nb_truediv 插槽。即使这样也不会改变一切。


更好的解决方案是构建一个导入钩子,在编译 AST 之前对其进行转换。

编写导入钩子可能是一个太大的话题,无法在几段中作为答案的前言进行介绍,因此请参阅 this question 了解该部分。

现在,至于 import 钩子的实际作用,您要做的是替换 MyLoader.exec_module 方法。而不是这个:

def exec_module(self, module):
    with open(self.filename) as f:
        data = f.read()

    # manipulate data some way...

    exec(data, vars(module))

你要这样做:

def exec_module(self, module):
    with open(self.filename) as f:
        data = f.read()

    tree = ast.parse(data)

    # manipulate tree in some way

    code = compile(tree, self.filename, 'exec')
    exec(code, vars(module))

那么,我们如何“以某种方式操纵树”?通过构建NodeTransformer

每个/ 表达式都是一个BinOp 节点,其中op 是没有属性的Div 节点,leftright 是要划分的值。如果我们想将其更改为相同的表达式,但使用//,那就是相同的BinOp,但opFloorDiv

所以,我们可以只访问Div 节点并将它们变成FloorDiv 节点:

class DivTransformer(ast.NodeTransformer):
    def visit_Div(self, node):
        return ast.copy_location(ast.FloorDiv(), node)

我们的“#以某种方式操作树”变成:

tree = DivTransformer().visit(tree)

如果您想根据除数是否为整数文字在floordivtruediv 之间进行选择,正如您的示例所暗示的那样,这并不难:

class DivTransformer(ast.NodeTransformer):
    def visit_BinOp(self, node):
        if isinstance(node.op, ast.Div):
            if isinstance(node.right, ast.Num) and isinstance(node.right.val, int):
                return ast.copy_location(ast.BinOp(
                    left=node.left,
                    op=ast.copy_location(ast.FloorDiv(), node.op),
                    right=node.right))
        return node

但我怀疑那是你真正想要的。事实上,你真正想要的可能很难定义。你可能想要这样的东西:

floordiv 如果两个参数在运行时都是整数值 floordiv 如果最终控制 __*div__/__*rdiv__ 的参数(通过精确复制解释器为此使用的规则)是一个整数值。 ……还有别的吗?

无论如何,这样做的唯一方法是将BinOp 替换为Call 到您编写的mydiv 函数,例如,坚持builtins。然后该函数执行类型切换以及实现您的规则所需的任何其他内容,然后是 return a/breturn a//b

【讨论】:

他们希望将 a/2. 视为 truediv,而不是将所有除法转换为 floordiv。 @user2357112 我为此添加了一个部分。如前所述,这不是一个明确定义的要求。弄清楚如何定义它实际上是困难的部分。例如,即使a 是浮点数,他们是否真的希望a/2 成为truediv? @abarnert 谢谢你的回答。我将进一步调查它,因为我是 Python 新手。但是,为了回答您的问题,理想情况下,我希望这样做:python3.interpret_like_python2x(code_v2x)。如果我使用 python v2.x 解释器,我想得到相同的结果。 @tsahmatsis 是code_v2x 源代码吗?一个函数对象?模块名称? ……?而且,正如我在问题中提到的:有没有理由不能只是subprocess.run(['python2.5', 'code_v2x.py', … some args …], …)?因为那将是迄今为止最简单的事情。 @abarnert code_v2x 是源代码,其解释方式应与使用 v3.x 的 Python v2.x 相同。

以上是关于使用 Python v3.x 和 C API 解释 Python v2.5 代码(关于整数除法)的主要内容,如果未能解决你的问题,请参考以下文章

Python C API 和 C++ 函数

1.1.1. Atitit Cocos2d-JS v3.x的问题

C ++中的独立Python解释器[重复]

Python / C-Api:向模块添加类

Python 的 C API 的 const 正确性

在C,C++,java和python运行时解释器和编译器的区别