1

I have a v2.5 Python code which I cannot control, as it is being exported from a third party software which supports Python v2.5.

I have Python v3.3 on my machine and I want, somehow, to emulate the v2.5 using the C API. My main concern is the integer division which differs between v2.x and v3.x.

For example I have the code below:

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

I want somehow this to be interpreted (using the v3.x) as:

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

Can I do something about that? Is there any way to interpret the code as if I had Python v2.5? I suppose that the 2to3 script does not work for my case, neither the six module.

I also found this question relative to mine: Python 2 and Python 3 dual development

Thanks

10
  • You mean that a//2 does not work? Python3-x explicitly defines that as "integer division". Commented Jul 13, 2018 at 20:03
  • @usr2564301 no, he's saying that he wants his Python3 interpreter to parse b = a/2 in a Python2 context, which is to say: b = a//2 Commented Jul 13, 2018 at 20:04
  • 1
    Port the third party library to python 3. Both python 2.5 and python 3.3 have reached support end of life years ago. Commented Jul 13, 2018 at 20:28
  • 1
    Or just run the Python 2.5 code as a subprocess in Python 2.5. Commented Jul 13, 2018 at 20:49
  • 1
    "I have Python v3.3 on my machine" go get 2.5 as well then. The package manager conda is pretty great about treating python itself as a package so you can create separate environments for separate versions. Commented Jul 13, 2018 at 21:01

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

6 Comments

They want to treat a/2. as truediv instead of converting all division to floordiv, though.
@user2357112 I added a section to that effect. As stated, that isn't a well-defined requirement. And figuring out how to define it is actually the hard part. For example, do they really want a/2 to be truediv even if a is a float?
@abarnert Thank you for the answer. Ι will further investigate it as I am new to Python. However, in order to answer to your question, I want, ideally, to do this: python3.interpret_like_python2x(code_v2x). I want to take the same results as those I would have obtained if I had used the python v2.x interpreter.
@tsahmatsis Is code_v2x source code? A function object? A module name? …? And, as I mentioned in the question: is there a reason that can't just be subprocess.run(['python2.5', 'code_v2x.py', … some args …], …)? Because that would be by far the easiest thing to do.
@abarnert code_v2x is source code that should be interpreted in the same way as Python v2.x does by using v3.x.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.