开发者

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, where x 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 of a.__iadd__([123]) and consequently the execution of a = Broken.__iadd__(a,[123])

  • what is really executed is then list.__iadd__(a,[123]) that returns to the block of Broken.__iadd__() the object modified in_place . BUT this precise object isn't returned by Broken.__iadd__() , SO this latter function ends by returning None .

  • so the result of Broken.__iadd__(a,[123]) is None , and this None is assigned to a

  • anyway, during the process the object to which a was initially pointing , and to which b 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 identifier a 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 the t[0]'s method __iadd__() to execute the following action t[0] .__iadd__([4744])

  • t[0] being a list, this execution is concretely t[0] = list.__iadd__( t[0] ,[4744])

  • so, the list referenced by t[0] is first modified in-place

  • and 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 objects

  • but before putting the address at the position t[0] , the interpreter verifies that the type of t 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 a TypeError 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 by t[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)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜