Вопрос стиля суммирования Python - PullRequest
1 голос
/ 16 апреля 2011

Что более питонично?

    T = 0
    for a in G:
        T += a.f()

или

    T = sum(a.f() for a in G)

Ответы [ 4 ]

8 голосов
/ 16 апреля 2011

Последний.Имя связывается только один раз, а не n + 1 раз.

6 голосов
/ 16 апреля 2011

Последний. Это всего лишь одна строка, и вы быстро видите, что она делает. Кроме того, есть только один доступ к T, который может быть немного быстрее.

2 голосов
/ 18 апреля 2011

ncoghlan прав: в более строгом смысле расширенное назначение всегда перепривязывает имя или подразумеваемый предмет.Но, поскольку его пост довольно эллиптический, я попытаюсь дать более существенные объяснения.

.

В документе:

object.__iadd__(self, other)

object.__isub__(self, other)

и т. Д. *

Эти методы вызываются для реализации расширенных арифметических назначений (+ =, - = и т. Д.).Эти методы должны попытаться выполнить операцию на месте (изменение self) и вернуть результат (который может быть, но не обязательно, self).(....)

Например, для выполнения инструкции x += y, где x - это экземпляр класса, который имеет метод __iadd__(), x.__iadd__(y).

http://docs.python.org/reference/datamodel.html#object.__add__

Я понимаю это следующим образом:

Если x является экземпляром, имеющим метод __iadd__(), инструкция x += y инициирует вызовтакого метода: x.__iadd__(y), который, как я понимаю, выполняется как x = __iadd__(x,y)

То есть: __iadd__() является функцией двух аргументов (см. в http://docs.python.org/tutorial/classes.html#method-objects) и, как и каждая функция, __iadd__(x,y) возвращает что-то, что назначено на x. Это что-то - это объект x, модифицированный на месте, с тем же адресом в памяти, если операция на местебыло возможно, в противном случае это другой объект с другим адресом, который возвращается и присваивается x.

Таким образом, строго говоря, coghlan прав: всегда выполняется операция повторного связывания идентификатора x,даже если __iadd__() был выполнен и тот же объект был изменен на местеОна была возвращена функцией __iadd__().Когда я пишу «тот же объект» , это означает «по тому же адресу» .В данном случае это бесполезная работа, которую выполняет Python, которая ничего не меняет на адрес, на который указывает идентификатор x, но Python делает это в любом случае.

Перепривязка к тому же объекту, измененному на месте из-заx += y в обычных случаях, когда x имеет метод __iadd__(), является причиной следующего результата:

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)

дает

b == []   id(b) == 18691096
a == []   id(a) == 18691096

b == [123]   id(b) == 18691096
a == [123]   id(a) == 18691096

Сказать, что a не был перебазирован в этом примере, имеет своего рода смысл только на уровне появления результата, с условием, что "rebind" берется со значением "rebind toдругой объект по другому адресу ", что не является строгим смыслом.В действительности, на уровне конкретных операций, повторное связывание эффективно обрабатывается.

.

В следующем примере:

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!?

, что дает

b == []   id(b) == 18711152
a == []   id(a) == 18711152

b == [123]   id(b) == 18711152
a == None   id(a) == 505338052

метод Broken __iadd__() является нерегулярным, поскольку в его коде нет оператора return.Вот почему Коглан называет это Broken, кстати.

Что происходит так:

  • инструкция a += [123] запускает выполнение a.__iadd__([123]) иследовательно, выполнение a = Broken.__iadd__(a,[123])

  • в действительности выполняется тогда list.__iadd__(a,[123]), который возвращает блоку Broken.__iadd__() объект, модифицированный in_place. НО этот точный объект не возвращается Broken.__iadd__(), SO , последняя функция завершается возвратом None.

  • , поэтомурезультат Broken.__iadd__(a,[123]) равен None, и этот None в любом случае присваивается a

  • , во время процесса объект, на который изначально указывал a, ина который все еще указывает b, был изменен на месте.

  • Это заканчивается идентификатором b, который абсолютно не был повторно привязан, и который указывает на измененный объект в том же месте в памяти, и идентификатором a, который имеетбыл полностью переписан в другом месте.

.

Этот код хорош: он сохраняет операцию, выполняемую функцией list.__iadd__(), и аннулирует присвоение своего результата, обычно выполняемого методом a __iadd__().Показывая, что следствием этого уничтожения является другой результат для a += [123], это доказывает, что это назначение действительно существует.Это доказательство подтверждается тем фактом, что восстановление оператора return восстанавливает нормальное поведение:

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

дает

b == []   id(b) == 18711200
a == []   id(a) == 18711200

b == [123]   id(b) == 18711200
a == [123]   id(a) == 18711200

.

wmwmwmwmwmwmwmwmwmwmwmwmwmwmwmwmmmmwmwmwmwm

* 50.

Следующий фрагмент:

t = ([],)
t[0] += [145]

приводит к

TypeError: 'tuple' object does not support item assignment

Но эта ошибка связана с тем, что значение элемента кортежа не можетизменить или на тот факт, что кортеж отклоняет процесс присваивания?

Вторая причина - правильная, поскольку следующий код доказывает, что значение кортежа может измениться:

t = ([],)
print 't before ==',t,'  id(t) ==',id(t)
el = t[0]
el += [608]
print 't after ==',t,'  id(t) ==',id(t)

это дает другое значение одному и тому же объекту (в том же месте):

t before == ([],)   id(t) == 11891216
t after == ([608],)   id(t) == 11891216

Причина в том, что хотя кортеж неизменен, его значение может измениться:

Значение неизменяемого контейнерного объекта, который содержит ссылку на изменяемый объект, может изменяться при изменении значения последнего;однако контейнер все еще считается неизменным, потому что коллекция объектов, которую он содержит, не может быть изменена.Таким образом, неизменность - это не то же самое, что наличие неизменяемой ценности, она более тонкая.Изменчивость объекта определяется его типом

http://docs.python.org/reference/datamodel.html#objects-values-and-types

.

Эта тонкость позволяет написать код, который лучше указывает, что происходит под ним:

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

Поведение аналогично поведению первого фрагмента с использованием Broken ():

  • инструкция t[0] += [4744] вызываетt[0] метод __iadd__() для выполнения следующего действия t[0] .__iadd__([4744])

  • t[0] в виде списка, это выполнение конкретно t[0] = list.__iadd__( t[0] ,[4744])

  • так, список, на который ссылается t[0], сначала изменяется на месте

  • , а затем выполняется попытка выполнить назначение

  • эта попытка не является попыткой привязать имя к измененному объекту на месте (имя не подразумевается);он состоит в попытке привязать элемент (позицию в составном объекте) к объекту, то есть здесь поместить адрес модифицированного объекта на месте в положение, известное как t[0]: элементы составного объектав действительности это ссылки на другие объекты, то есть адреса этих других объектов

  • , но перед тем, как поместить адрес в положение t[0], интерпретатор проверяет, что тип t разрешить эту операцию: вот как я понимаю предложение, приведенное выше: «Изменчивость объекта определяется его типом» .В этот момент интерпретатор обнаруживает, что ему не разрешено выполнять операцию, и TypeError повышается.

  • в моем коде, эта ошибка перехватывается try-exceptи этот трюк позволяет видеть в исключающей части, что объект, на который ссылается t[0], действительно был изменен.

.

Итак, аналогичный способ былвзято как во фрагменте с Broken(): отделяет наблюдение за изменением на месте от наблюдения за назначением.

Разница в том, что здесь назначение присваивается в качестве доказательства вследствие его действия, а не вследствие его отсутствия.

.

wmwmwmwmwmwmwmwm

Хитрые отрывки, я нахожу.Мне было трудно понять эти основные механизмы.

1 голос
/ 18 апреля 2011

Чтобы уточнить мои комментарии к ответу Игнасио относительно расширенного назначения, всегда связывающего имя, даже для изменяемых объектов, я предлагаю следующий пример:

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!?

Как я уже сказал в своем комментарии, расширенное назначение фактически расширяется до значения, эквивалентного a = a.__iadd__([123]) для изменяемых объектов. Другой классический способ продемонстрировать это с помощью кортежей:

>>> 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]

Ошибка в приведенном выше примере Broken заключается в том, что не возвращается значение из метода __iadd__. Оно должно заканчиваться return self или просто возвращать результат вызова до метода родительского класса.

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)
...