This sounds like a bad idea—and you're going to have much more serious problems interpreting Python 2.5 code as Python 3, like every except
statement being a syntax error, and strings being the wrong type (or, if you fix that, s[i]
returning an int rather than a bytes), and so on.
The obvious thing to do here is to port the code to a Python that's still supported.
If that really is impossible for some reason, the simplest thing to do is probably to write a trivial Python 2.5 wrapper around the code you need to run, which takes its input via sys.argv
and/or sys.stdin
and returns results via sys.exit
and/or sys.stdout
.
Then, you just call it like this:
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')
But if you really want to do it, and this is really your only problem… this still isn't the way to do it.
You'd have to go below the level of the C API, into the internal type objects like struct PyFloat_Type
, access their tp_as_number
structs, and copy their nb_floordiv
functions to their nb_truediv
slots. And even that may not change everything.
A much better solution is to build an import hook that transforms the AST before compiling it.
Writing an import hook is probably too big a topic to cover in a couple of paragraphs as a preface to an answer, so see this question for that part.
Now, as for what the import hook actually does, what you want to do is replace the MyLoader.exec_module
method. Instead of this:
def exec_module(self, module):
with open(self.filename) as f:
data = f.read()
# manipulate data some way...
exec(data, vars(module))
You're going to do this:
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))
So, how do we "manipulate tree in some way"? By building a NodeTransformer
.
Every /
expression is a BinOp
node, where the op
is Div
node with no attributes, and the left
and right
are the values to divide. If we want to change it into the same expression but with //
, that's the same BinOp
, but where the op
is FloorDiv
.
So, we can just visit Div
nodes and turn them into FloorDiv
nodes:
class DivTransformer(ast.NodeTransformer):
def visit_Div(self, node):
return ast.copy_location(ast.FloorDiv(), node)
And our "# manipulate tree in some way" becomes:
tree = DivTransformer().visit(tree)
If you want to choose between floordiv
and truediv
depending on whether the divisor is an integral literal, as your examples seem to imply, that's not much harder:
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
But I doubt that's what you actually want. In fact, what you actually want is probably pretty hard to define. You probably want something like:
floordiv
if both arguments, at runtime, are integral values
floordiv
if the argument that will end up in control of the __*div__
/__*rdiv__
(by exactly reproducing the rules used by the interpreter for that) is an integral value.
- … something else?
Anyway, the only way to do this is to replace the BinOp
with a Call
to a mydiv
function, that you write and, e.g., stick in builtins
. That function then does the type-switching and whatever else is needed to implement your rule, and then either return a/b
or return a//b
.