I find the second and third versions quite jarring, because they mix up two paradigms.
Code:
for o in os:
o.double()
is
imperative code. You are telling the computer what to do.
Code:
[o.double() for o in os]
is
declarative. It looks like it describes something. In fact it does. It is an expression whose value is a new list [None, None, None, ... None]. I'm surprised by this list -- it doesn't seem like producing it was the desired effect of the code -- and I may also be surprised that the code has side effects.
In a purely functional language, it's difficult to write code that affects state, and you tend to isolate those parts of the code that must perform input and output or other side effects. In a procedural or imperative language, it's difficult, ugly or impossible to write declarative code, so you tend to change state with every line of code. Python gives you both, and each has its place.
If you find the map and list comprehensions flow better, maybe it would be good to write the whole program in a more functional style. So you would have
Code:
class O:
...
def double(self):
return self.i * 2 # or return O(self.i * 2) if that is what is desired
or
Code:
def double(o):
return O(o.i * 2)
class O:
...