Python summation style question
Which is开发者_JAVA百科 more pythonic?
T = 0
for a in G:
T += a.f()
or
T = sum(a.f() for a in G)
The latter. The name is only bound once, as opposed to happening n+1 times.
The latter. It is just one line and you quickly see what it's doing. Besides that, there's only one access to T
which might be a little bit faster.
ncoghlan is right: in the more rigorous sense, an augmented assignement always rebinds the name or the item implied. But, as his post is rather elliptic, I'll try to give more subtantial explanations.
.
In the doc:
object.__iadd__(self, other)
object.__isub__(self, other)
etc...
These methods are called to implement the augmented arithmetic assignments (+=, -=, etc...). These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self). (....)
For instance, to execute the statement
x += y
, wherex
is an instance of a class that has an__iadd__()
method ,x.__iadd__(y
) is called.http://docs.python.org/reference/datamodel.html#object.__add__
I understand this as follows:
If x
is an instance having the method __iadd__()
, the instruction x += y
triggers the call of this method like that : x.__iadd__(y)
which executes, as I understand, as x = __iadd__(x,y)
That is to say: __iadd__()
is a function of two arguments (see in http://docs.python.org/tutorial/classes.html#method-objects) and , as every function, __iadd__(x,y)
returns something that is assigned to x
. This something is the object x
modified in-place, with the same address in memory, if the in-place operation was possible; otherwise it is another object , with another address that is returned and asigned to x
.
So, strictly speaking, coghlan is right: the operation of rebinding the identifier x
is always performed, even if the __iadd__()
has been executed and the same object modified in-place has been returned by the function __iadd__()
. When I write «the same object» , it means « at the same address » . In this case, it's a useless work that Python does, that changes nothing to the address towards which the identifier x
points, but Python does it anyway.
The rebinding to the same object modified in-place due to x += y
in normal cases, when x
has a method __iadd__()
, is the reason of the following result:
a = b = []
print 'b ==',b,' id(b) ==',id(b)
print 'a ==',a,' id(a) ==',id(a)
a += [123]
print '\nb ==',b,' id(b) ==',id(b) # OK
print 'a ==',a,' id(a) ==',id(a)
gives
b == [] id(b) == 18691096
a == [] id(a) == 18691096
b == [123] id(b) == 18691096
a == [123] id(a) == 18691096
Saying that a
hasn't been rebinded in this exemple has a sort of sense only at the level of the appearance of the result, with the condition that "rebind" is taken with the meaning of "rebind to another object at nother address" , which is not the rigorous sense. In reality, at the concrete operations level, a rebind is effectively processed.
.
In the following exemple:
class Broken(list):
def __iadd__(self, other):
list.__iadd__(self, other)
a = b = Broken()
print 'b ==',b,' id(b) ==',id(b)
print 'a ==',a,' id(a) ==',id(a)
a += [123]
print '\nb ==',b,' id(b) ==',id(b) # OK
print 'a ==',a,' id(a) ==',id(a) # What!?
that gives
b == [] id(b) == 18711152
a == [] id(a) == 18711152
b == [123] id(b) == 18711152
a == None id(a) == 505338052
the Broken
's method __iadd__()
is irregular, since there is no return
statement in its code. That's why coghlan names it Broken
, by the way.
What is happening is this:
the instruction
a += [123]
triggers the execution ofa.__iadd__([123])
and consequently the execution ofa = Broken.__iadd__(a,[123])
what is really executed is then
list.__iadd__(a,[123])
that returns to the block ofBroken.__iadd__()
the object modified in_place . BUT this precise object isn't returned byBroken.__iadd__()
, SO this latter function ends by returningNone
.so the result of
Broken.__iadd__(a,[123])
isNone
, and thisNone
is assigned toa
anyway, during the process the object to which
a
was initially pointing , and to whichb
is still pointing, has been modified in place.That ends in an identifier
b
that has absolutely not been rebinded and that points to a modified object at the same place in the memory, and in an identifiera
that has been rebinded completely elsewhere.
.
This code is fine: it keeps the operation performed by the function list.__iadd__()
and it annihilates the assignement of its result normaly performed by the a
's method __iadd__()
. Showing that the consequence of this annihilation is a different result for a += [123]
, it proves that this assignement really exists. This proof is confirmed by the fact that restoring the return statement restores the normal behavior:
class UnBroken(list):
def __iadd__(self, other):
return list.__iadd__(self, other)
a = b = UnBroken()
print 'b ==',b,' id(b) ==',id(b)
print 'a ==',a,' id(a) ==',id(a)
a += [123]
print '\nb ==',b,' id(b) ==',id(b) # OK
print 'a ==',a,' id(a) ==',id(a) # OK
gives
b == [] id(b) == 18711200
a == [] id(a) == 18711200
b == [123] id(b) == 18711200
a == [123] id(a) == 18711200
.
wmwmwmwmwmwmwmwmwmwmwmwmwmwmwmwmwmwm
.
The following snippet:
t = ([],)
t[0] += [145]
results in
TypeError: 'tuple' object does not support item assignment
But, is this error due to the fact that the value of a tuple's element can't change or to the fact that a tuple refuses the process of assignement ?
The second reason is the right one, since the following code proves that the value of a tuple can change:
t = ([],)
print 't before ==',t,' id(t) ==',id(t)
el = t[0]
el += [608]
print 't after ==',t,' id(t) ==',id(t)
that gives a different value to the same object (at the same place):
t before == ([],) id(t) == 11891216
t after == ([608],) id(t) == 11891216
The reason is that though a tuple is immutable, its value can change:
The value of an immutable container object that contains a reference to a mutable object can change when the latter’s value is changed; however the container is still considered immutable, because the collection of objects it contains cannot be changed. So, immutability is not strictly the same as having an unchangeable value, it is more subtle. An object’s mutability is determined by its type
http://docs.python.org/reference/datamodel.html#objects-values-and-types
.
This subtlety allows to write a code that better points out what is going underneath :
t = ([],)
print 't before ==',t,' id(t) ==',id(t)
print 't[0] ==',t[0],' id(t[0]) ==',id(t[0])
try:
t[0] += [4744]
except TypeError:
print 't after ==',t,' id(t) ==',id(t)
print 't[0] ==',t[0],' id(t[0]) ==',id(t[0])
t[0] += [8000]
result
t before == ([],) id(t) == 18707856
t[0] == [] id(t[0]) == 18720608
t after == ([4744],) id(t) == 18707856
t[0] == [4744] id(t[0]) == 18720608
Traceback (most recent call last):
File "I:\what.py", line 64, in <module>
t[0] += [8000]
TypeError: 'tuple' object does not support item assignment
The behavior is similar to the first snippet's behavior using Broken() :
the instruction
t[0] += [4744]
triggers thet[0]
's method__iadd__()
to execute the following actiont[0] .__iadd__([4744])
t[0]
being a list, this execution is concretelyt[0] = list.__iadd__( t[0] ,[4744])
so, the list referenced by
t[0]
is first modified in-placeand then, an attempt to make the assignement is done
this attempt is not an attempt to bind a name to the in-place modified object (there is no name implied); it consists in trying to bind an item (a position in a composite object) to an object, that is to say here to put the address of the in-place modified object at the position known as
t[0]
: elements of a composite object are in facts references to other objects, that is to say addresses of these other objectsbut before putting the address at the position
t[0]
, the interpreter verifies that the type oft
allow this operation : that is how I understand the sentence seen above: « An object’s mutability is determined by its type » . At this moment, the interpreter detects that it isn't allowed to perform the operation and aTypeError
is raised.in my code, this error is intercepted by a
try-except
, and this trick allows to see in the except part that the object referenced byt[0]
has really been modified.
.
So, a similar way has been taken like in the snippet with Broken()
: separating the observation of the in-place modification from the observation of the assignement.
The difference is that here the assignement is put in evidence by the consequence of its action, not by the consequence of its absence.
.
wmwmwmwmwmwmwmwm
Tricky snippets, I find. I had a hard time to understand these underlying mechanisms.
To clarify my comments on Ignacio's answer regarding augmented assignment always rebinding the name, even for mutable objects, I offer the following example:
class Broken(list):
def __iadd__(self, other):
list.__iadd__(self, other) # This is not quite right
a = b = Broken()
a += [123]
print(b) # OK
print(a) # What!?
As I said in my comment, the augmented assignment actually expands to something equivalent to a = a.__iadd__([123])
for mutable objects. The other classic way to demonstrate this is with tuples:
>>> t = [],
>>> t[0] += [123]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t[0]
[123]
The bug in the Broken
example above is to not return a value from the __iadd__
method. It should either end with return self
or else just return the result of the call up to the parent class method.
class Correct(list):
def __iadd__(self, other):
list.__iadd__(self, other)
return self
class AlsoCorrect(list):
def __iadd__(self, other):
return list.__iadd__(self, other)
精彩评论