1

In Python (more specifically Python 3.x), what happens if I say x = x, where x is either a reference to a mutable (like list) or a reference to an immutable (like int) on the low-level? Does the compiler simply ignore such nonsense?

More specifically, what does the compiler do if we have the following case:

class A:
    def __init__(self):
        self.a = self.init_a()

    def init_a(self):
        self.a = some_value
        """
        do stuff with self.a here
        """
        return self.a

For those who haven't noticed, self.a effectively gets assigned to itself through the function, init_a(self).

I know this case with class A above seems silly, but I am trying to keep my code clean and readable by clearly initializing all my member variables inside the __init__(self) function (in a different class that I am implementing for real). I just want to know for interest's sake whether the compiler optimizes that step away, or whether it does some operations in any case, even though the statement does not result in anything (I'm guessing it gets optimized away, but I want to be sure - you never know).

Konrad
  • 2,207
  • 4
  • 20
  • 31
  • You should just call it `a` inside `init_a` and only attach it to `self` in `__init__`. Otherwise you contradict your goal or "clearly initializing all my member variables inside the `__init__(self)` function". – Alex Hall May 28 '16 at 23:44
  • Yes, that's probably a better idea, but I was wondering about the case above in any case – Konrad May 28 '16 at 23:46

3 Answers3

5
from dis import dis

def foo():
    x = 1
    x = x

dis(foo)

Result:

  4           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (x)

  5           6 LOAD_FAST                0 (x)
              9 STORE_FAST               0 (x)
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE 

This shows that even in the most trivial case, x = x is NOT optimised away (in CPython, which is what dis is for).

Alex Hall
  • 34,833
  • 5
  • 57
  • 89
  • Wow, did not expect that. Thanks for showing the actual code on how to obtain that bytecode - know now what library to use for that (and that there is a library for that) – Konrad May 28 '16 at 23:51
1

I just want to know for interest's sake whether the compiler optimizes that step away, or whether it does some operations in any case, even though the statement does not result in anything.

Unless the Python Language Reference says something about it1, then any optimization or lack of it is implementation specific2. You can investigate what a particular Python implementation does with this, but you cannot generalize to all Python implementations.


1 - My cursory search of this copy of the spec does not real any relevant discussion of optimization. Ergo, my reading would be that valid optimizations are permitted but not required. (A valid optimization would be one that does not alter any observable behavior of a valid Python program that is clearly required by the spec.)

2 - Indeed the spec says this: "Each of these implementations [listed above in the spec] varies in some way from the language as documented in this manual, or introduces specific information beyond what’s covered in the standard Python documentation. Please refer to the implementation- specific documentation to determine what else you need to know about the specific implementation you’re using."

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Agreed - it did cross my mind that it might be implementation specific too, since there seems to be quite a few distinct Python interpreters. Since both Anaconda and Winpython interpreters give the same bytecode output as [Alex Hall's answer](http://stackoverflow.com/a/37504798/4475902), CPython 3.x probably does not optimise this away (assuming Anaconda and WinPython use CPython, which is probably the case) – Konrad May 29 '16 at 00:11
  • 1
    Yes ... but what happens when the bytecodes are compiled to native code? For instance on a JVM platform, I would *expect* the JIT compiler to optimize away any redundant assignments. – Stephen C May 29 '16 at 00:25
  • I am reading in [this](http://stackoverflow.com/a/1644742/4475902) answer and [this](http://stackoverflow.com/a/6889798/4475902) answer that CPython interprets its bytecode, which means that it reads it instruction by instruction (i.e. no optimizations are done further) if I am correct. But it seems that PyPy does compile the bytecode, as [this](http://stackoverflow.com/a/6889798/4475902) answer indicates, so it is possible that PyPy might do such optimizations? (it is too late for me to read through the documentation of Cpython and PyPy to confirm this - I will trust those answerers this time – Konrad May 29 '16 at 00:39
1

This may seem very strange, but it is not actually possible to implement the optimization you suggested, as python is far too dynamic.

Consider the following code:

[Definition of your A class]
@property
def sneaky(self):
    return self._a
@sneaky.setter
def sneaky(self, value):
    self._a = value * 2
A.a = sneaky
a = A()

The self.a = in the __init__ method suddenly starts having an effect! It doubles the value of a. Python is very dynamic indeed.

pppery
  • 3,731
  • 22
  • 33
  • 46
  • Very true. I actually thought of descriptors a while after I asked this question. For example if we have a descriptor, with getter and setter defined for `A`'s member `a`, then `A_obj.a = A_obj.a` first calls the getter then the setter, so it cannot be optimized away, as you say. As I understand it, descriptors form the basis of a lot of Python's functionality, since [here](https://docs.python.org/3/howto/descriptor.html) they state "Learning about descriptors...creates a deeper understanding of how Python works and an appreciation for the elegance of its design." (continued below) – Konrad Jun 03 '16 at 09:43
  • So the getter and setter thing as described above probably happens quite often when using Python, unawares to the programmer. – Konrad Jun 03 '16 at 09:44
  • But it is an interesting example you posted above - very sneaky indeed! – Konrad Jun 03 '16 at 09:47