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

Nipun Thennakoon
  • 3,586
  • 1
  • 18
  • 26
tsahmatsis
  • 51
  • 5
  • You mean that `a//2` does not work? Python3-x [explicitly](https://docs.python.org/3.1/tutorial/introduction.html#numbers) defines that as "integer division". – Jongware Jul 13 '18 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` – Adam Smith Jul 13 '18 at 20:04
  • @AdamSmith: if that is the case: well, for custom classes at least, Python allows redefinition of operators. I am too new to Python to know top of my head if that goes as well for built-in classes such as `integer` and (per OP's wishlist) `float`. – Jongware Jul 13 '18 at 20:06
  • @AdamSmith Correct! that is what I am saying – tsahmatsis Jul 13 '18 at 20:07
  • ... [my question for old Python 2](https://stackoverflow.com/q/19404796) gets a resounding "no", and I have not read that Python-3.x had any significant change in this. So that seems to be the answer: no you cannot. – Jongware Jul 13 '18 at 20:10
  • 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. – Håken Lid Jul 13 '18 at 20:28
  • 1
    Or just run the Python 2.5 code as a subprocess in Python 2.5. – abarnert Jul 13 '18 at 20:49
  • 1
    *"I have Python v3.3 on my machine"* go get 2.5 as well then. The package manager [**conda**](https://conda.io/docs/index.html) is pretty great about [treating python itself as a package](https://conda.io/docs/user-guide/getting-started.html#managing-python) so you can create separate environments for separate versions. – Aaron Jul 13 '18 at 21:01
  • @HåkenLid I think this is the best solution. To run the 2.5 as a subprocess. So I need to have v2.5 as package to my machine as well... – tsahmatsis Jul 14 '18 at 09:07
  • @Aaron Thanks for the information – tsahmatsis Jul 14 '18 at 11:16

1 Answers1

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.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • They want to treat `a/2.` as truediv instead of converting all division to floordiv, though. – user2357112 Jul 13 '18 at 20:25
  • @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 Jul 13 '18 at 20:36
  • @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 Jul 14 '18 at 05:53
  • @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 Jul 14 '18 at 07:15
  • @abarnert code_v2x is source code that should be interpreted in the same way as Python v2.x does by using v3.x. – tsahmatsis Jul 14 '18 at 09:01
  • @abarnert I think this is the best solution. To run the 2.5 as a subprocess. So I need to have v2.5 as package to my machine as well... – tsahmatsis Jul 14 '18 at 09:07