2 апреля 2010 в 08:23

Code Like a Pythonista: Idiomatic Python (part1) перевод

Kaa, the Python


Это продолжение перевода статьи Дэвида Гуджера «Пиши код, как настоящий Питонист: идиоматика Python»

Начало и окончание перевода.


Спасибо всем хабраюзерам за оценки первой части, ценные замечания и положительные комментарии. Постарался учесть ошибки, снова жду конструктивного обсуждения.





Используйте in когда возможно (1)



Хорошо:

for key in d:
    print key

  • in обычно быстрее.
  • этот способ работает также для элементов произвольных контейнеров (таких как списки, кортежи, множества).
  • in является так же оператором (как мы увидим).


Плохо:
for key in d.keys():
    print key


Это относится также ко всем объектам с методом keys()


Используйте in когда возможно (2)



Но .keys() необходим при изменении словаря:

for key in d.keys():
    d[str(key)] = d[key]


d.keys() создает статический список из ключей словаря. В противном случае, вы получили бы исключение «RuntimeError: dictionary changed size during iteration» (Изменился размер словаря во время итерации).

Правильнее использовать key in dict, а не dict.has_key():

# Делайте так:
if key in d:
    ...do something with d[key]

# а не так:
if d.has_key(key):
    ...do something with d[key]

Этот код использует in как оператор.

Метод словарей get



Часто нам нужно заполнить словарь данными перед использованием.

Наивный способ сделать это:

navs = {}
for (portfolio, equity, position) in data:
    if portfolio not in navs:
        navs[portfolio] = 0
    navs[portfolio] += position * prices[equity]


dict.get(key, default) позволяет избежать проверок:

navs = {}
for (portfolio, equity, position) in data:
    navs[portfolio] = (navs.get(portfolio, 0)
                       + position * prices[equity])


Так более правильно.

Метод словарей setdefault (1)



Пусть теперь мы должны инициализировать значения элементов нестатичного словаря, где каждый элемент представлен списком. Вот еще один наивный способ:

Инициализация элементов изменяемого словаря:
equities = {}
for (portfolio, equity) in data:
    if portfolio in equities:
        equities[portfolio].append(equity)
    else:
        equities[portfolio] = [equity]

dict.setdefault(key, default) делает эту работу более эффективно:

equities = {}
for (portfolio, equity) in data:
    equities.setdefault(portfolio, []).append(
                                         equity)

dict.setdefault() эквивалентно «получить, или установить и получить» («get, or set & get»). Или «установить если нужно, потом получить» («set if necessary, then get»). Это особенно эффективно, если ключ словаря сложно вычислить или его долго набирать с клавиатуры.

Только есть проблема с dict.setdefault(), она состоит в том, что дефолтное значение вычисляется всегда, независимо от того, нужно оно или нет. Это важно, если значение по умолчанию затратно вычислять.

Если дефолтное значение сложно вычисляется, может оказаться удобнее использовать класс defaultdict, который мы кратко рассмотрим.

Метод словарей setdefault (2)



Тут мы видим, как setdefault метод также может быть отдельно используемым выражением:

navs = {}
for (portfolio, equity, position) in data:
    navs.setdefault(portfolio, 0)
    navs[portfolio] += position * prices[equity]


Метод словаря setdefault возвращает значение по умолчанию, но мы игнорируем его здесь. Мы обходим побочный эффект setdefault, в результате которого присваивается значение еще не инициализированному словарному элементу.

defaultdict


Новое в Python 2.5.

defaultdict появился в Python 2.5, как часть модуля collections. defaultdict идентичен обычным словарям, за исключением двух вещей:

  • он принимает первым аргументом производящую функцию; и
  • когда ключ словаря встречается в первый раз, вызывается производящая функция, а ее результатом инициализируется значение нового элемента словаря.

Вот два способа получить defaultdict:
  • импортировать модуль collections и ссылаться через имя этого модуля,
  • или импортировать непосредственно имя defaultdict:

import collections
d = collections.defaultdict(...)

from collections import defaultdict
d = defaultdict(...)


Вот предыдущий пример, где каждый элемент словаря инициализируется как пустой список, переписанный с defaultdict:

from collections import defaultdict

equities = defaultdict(list)
for (portfolio, equity) in data:
    equities[portfolio].append(equity)

В этом случае производящая функция list, возвращающая пустой список.

А в этом примере показывается, как получить словарь со значением по умолчанию = 0: для этого используется производящая функция int:
navs = defaultdict(int)
for (portfolio, equity, position) in data:
    navs[portfolio] += position * prices[equity]


Все же будьте осторожны с defaultdict. Вы не сможете получить исключение KeyError из правильно инициализированного defaultdict. Можно использовать «key in dict» условие, если вам нужно проверить на существование конкретный ключ.

Составление и разбор словарей


Вот полезная техника для составления словаря из двух списков (или последовательностей): один — список из ключей, другой — из значений.

given = ['John', 'Eric', 'Terry', 'Michael']
family = ['Cleese', 'Idle', 'Gilliam', 'Palin']

pythons = dict(zip(given, family))

>>> pprint.pprint(pythons)
{'John': 'Cleese',
 'Michael': 'Palin',
 'Eric': 'Idle',
 'Terry': 'Gilliam'}


Обратное, разумеется, тривиально:

>>> pythons.keys()
['John', 'Michael', 'Eric', 'Terry']
>>> pythons.values()
['Cleese', 'Palin', 'Idle', 'Gilliam']


Отметьте, что порядок результатов .keys() и .values() отличается от порядка элементов, когда создается словарь словарь. Порядок на входе отличается от порядка на выходе. Это потому что словарь, в сущности, неупорядочен. Тем не менее, порядок вывода гарантируется для соответствия (порядок ключей корреспондируется с порядком значений), насколько возможно, если словарь не изменялся между вызовами.

Проверка на истинность значения



# делайте так:    # а не так:
if x:             if x == True:
    pass              pass


Это элегантный и эффективный способ для проверки истинности объектов Python(или Булевых значений).
Проверка списка:

# делать так:     # а не так:
if items:         if len(items) != 0:
    pass              pass

                  # и определенно не так:
                  if items != []:
                      pass


Значение Истины



Имена True и False — экземпляры встроенного булевского типа. Подобно None, создается только по одному экземпляру каждого из них.

False True
False (== 0) True (== 1)
"" (пустая строка) любая строка кроме "" (" ", «что-нибудь»)
0, 0.0 любое число кроме 0 (1, 0.1, -1, 3.14)
[], (), {}, set() любой не пустой контейнер ([0], (None,), [''])
None почти любой объект, который явно не False


Пример значения Истины в объектах:

>>> class C:
...  pass
...
>>> o = C()
>>> bool(o)
True
>>> bool(C)
True

(Примеры: выполните truth.py.)

Для контроля истинности экземпляров классов, определенных пользователем, используйте специальный метод __nonzero__ или __len__. Используйте __len__, если ваш класс — контейнер, имеющий длину:
class MyContainer(object):

    def __init__(self, data):
        self.data = data

    def __len__(self):
        """Return my length."""
        return len(self.data)

Если ваш класс — не контейнер, используйте __nonzero__:

class MyClass(object):

    def __init__(self, value):
        self.value = value

    def __nonzero__(self):
        """Return my truth value (True or False)."""
        # This could be arbitrarily complex:
        return bool(self.value)


В Python 3.0, __nonzero__переименована в __bool__ для единообразия со встроенным типом bool. Для совместимости добавьте этот код в определение класса:

__bool__ = __nonzero__


Индекс и элемент (Index & Item) (1)



Вот хитрый способ сохранить некоторый напечатанный текст в список из слов:

>>> items = 'zero one two three'.split()
>>> print items
['zero', 'one', 'two', 'three']


Скажем, мы хотим перебрать элементы и нам нужны как сами элементы, так и их индексы:

                  - или -
i = 0
for item in items:      for i in range(len(items)):
    print i, item               print i, items[i]
    i += 1


Индекс и элемент (Index & Item) (2): enumerate



Функция enumerate принимает аргументом список и возвращает пары (index, item) (номер, элемент):

>>> print list(enumerate(items))
[(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')]


Нам нужно преобразование в список list, чтобы получить полный результат, потому что enumerate — ленивая функция: она генерирует один элемент, пару, за один вызов, как бы «сколько спросили». Цикл for — как раз то место, которое перебирает список и вызывает по одному результату за проход. enumerate — пример генераторов (generator), которые мы рассмотрим более подробно позже. print не принимает по одному результату за раз — мы хотим общий результат, так что мы должны явно конвертировать генератор в список, когда хотим его напечатать.

Наш цикл становится значительно проще:

for (index, item) in enumerate(items):
    print index, item

# сравните:             # сравните:
index = 0               for i in range(len(items)):
for item in items:              print i, items[i]
    print index, item
    index += 1


Вариант с enumerate существенно короче и проще, чем способ слева, а также его легче прочесть и понять.

Пример, показывающий, как функция enumerate действительно возвращает итератор (генератор является разновидностью итератора):
>>> enumerate(items)
<enumerate object at 0x011EA1C0>;
>>> e = enumerate(items)
>>> e.next()
(0, 'zero')
>>> e.next()
(1, 'one')
>>> e.next()
(2, 'two')
>>> e.next()
(3, 'three')
>>> e.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
StopIteration


В других языках есть «переменные»


Во многих других языках, присваивание переменной помещает значение в ячейку.

int a = 1;

a1box.png


Ячейка «a» теперь содержит целое 1.

Присваивание другого значения той же переменной заменяет содержимое ячейки:
a = 2;

a2box.png


Теперь ячейка «a» содержит целое 2.

Присваивание одной переменной в другую создает копию значения и помещает его в новую ячейку:

int b = a;

b2box.png
a2box.png

«b» — вторая ячейка, с копией целого 2. Ячейка «a» имеет отдельную копию.

В Python есть «имена»


В Python, «имена» или «идентификаторы» похожи на ярлыки, (тэги, метки) прикрепленные к объекту.
a = 1

a1tag.png

Тут целое один имеет ярлык «a».

Если мы переназначаем «a», мы просто перемещаем ярлык на другой объект:
a = 2

a2tag.png
1.png


Теперь имя «a» прицеплено к целому объекту 2.
Исходный объект целого 1 больше не имеет метки «a». Он может еще немного пожить, но мы не можем получить его по имени «а». Когда объект больше не имеет ссылок или меток, он удаляется из памяти.

Если мы присваиваем одно имя другому, мы просто присоединяем другую метку к существующему объекту:
b = a

ab2tag.png


Имя «b» — это просто второй ярлык, назначенный тому же объекту, что и «a».

Хотя обычно мы говорим «переменные» в Python (потому что это общепризнанная терминология), мы в действительности имеем в виду «имена» или «идентификаторы». В Python «переменные» — это ссылки на значения, а не именованные ячейки.

Если вы еще ничего не получили из этого туториала, я надеюсь, вам понятно, как работают имена в Python. Четкое понимание несомненно сослужит добрую службу, поможет вам избежать случаев, подобных этому:
? (тут почему-то код примера отсутствует — прим. перев.)

Значения параметров по умолчанию



Это общая ошибка, которую часто допускают начинающие. Даже более продвинутые программисты допускают ее, если недостаточно понимают имена в Python.

def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list


Проблема здесь в том, что значение по умолчанию для a_list, пустого списка, вычисляется только во время определения функции. Таким образом, каждый раз, когда вы вызываете функцию, вы получаете одно и то же значение по умолчанию. Попробуйте вот это несколько раз:

>>> print bad_append('one')
['one']

>>> print bad_append('two')
['one', 'two']


Списки — изменяемые объекты, вы можете изменить их содержимое. Правильный способ получить список «по умолчанию» (или словарь, или множество) — создать его во время выполнения, а не в объявлении функции:

def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list


% -форматирование строк



В Python оператор % работает подобно функции sprintf из C.

Хотя, если вы не знаете C, это вам мало о чем говорит. В общем, вы задаете шаблон или формат и подставляете значения.

В этом примере, шаблон содержит две спецификации представления: "%s" означает «вставить строку здесь», а "%i" — «преобразовать целое в строку и вставить здесь». "%s" особенно полезна, потому что использует встроенную в Python функцию str() для преобразования любого объекта в строку.

Подставляемые значения должны соответствовать шаблону; у нас тут два значения, составленных в кортеж.
name = 'David'
messages = 3
text = ('Hello %s, you have %i messages'
        % (name, messages))
print text


Вывод:
Hello David, you have 3 messages


Подробнее — в Python Library Reference, раздел 2.3.6.2, «String Formatting Operations» (операции форматирования строк). Добавьте в закладки!
Если вы этого еще не сделали, зайдите на python.org, загрузите HTML документацию (в .zip или как понравится), и установите ее на своей машине. Нет ничего полезнее, как иметь полнейшее руководство у себя под рукой.

Расширенное % -форматирование строк



Многие не знают, что есть другие, более гибкие способы форматирования строк:

По имени со словарем:

values = {'name': name, 'messages': messages}
print ('Hello %(name)s, you have %(messages)i '
       'messages' % values)


Здесь мы определяем имена для подставляемых значений, которые ищутся в словаре.

Заметили избыточность? Имена «name» и «messages» уже определены в локальном
пространстве имен. Мы можем это улучшить.
По имени, используя локальное пространство имен:
print ('Hello %(name)s, you have %(messages)i '
       'messages' % locals())


Функция locals() возвращает словарь из всех идентификаторов доступных в локальном пространстве имен.

Это очень мощный инструмент. С ним вы можете форматировать все строки как вы захотите без необходимости беспокоиться о соответствии подставляемых значений в шаблон.
Но будьте осторожны. («С большой властью приходит большая ответственность.») Если вы используете locals() с внешне-связанными шаблонами строк, вы предоставляете ваше локальное пространство имен вызывающему. Это просто, чтоб вы знали.
Чтобы проверить ваше локальное пространство имен:
>>> from pprint import pprint
>>> pprint(locals())

pprint — тоже полезная функция. Если вы еще не знаете, попробуйте поиграться с ней. Она делает отладку ваших структур данных гораздо проще!

Расширенное % -форматирование строк



Пространство имен атрибутов экземпляра объекта — это просто словарь, self.__dict__.

По именам, используя пространство имен экземпляра:

print ("We found %(error_count)d errors"
       % self.__dict__)


Эквивалентно, но более гибко, чем:

print ("We found %d errors"
       % self.error_count)


Примечание: атрибуты класса в class __dict__. Просмотр пространства имен на самом деле заключается в поиске по словарю.

Заключительная часть перевода.
Автор оригинала: David Goodger
kossmak @kossmak
карма
52,0
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (23)

  • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    Бальзам на душу! Спасибо.
    Ждем следующую часть, хочется чего-нибудь посложней, какой-нибудь особенной магии Python. Из этой статьи, почти все используется ежедневно. Про locals() узнал в Django, только вот использовать его постоянно — не круто.
  • +3
    Есть замечательная книжка Саммерфилд'а yfpsdftncz Программирование на Python 3. Там есть все эти тонкости + еще огромная куча всего интересного. Книга включает в себя описание всего стандарта питона последней версии, так что советую полистать хотя бы тем кто этим занимается)
    • +4
      Книжку большая, долго читать. А тут раз в неделю принял пилюлю знаний — немного поумнел.
      • +1
        в чем то вы правы, но я думаю понимаете что если хочешь овладеть всеми возможностями языка то хочется читать материал который рассказывает обо всем и вся что касается языка, то есть я хочу сказать что прочитав статью вы не станете ближе к гуру питона а прочитав книгу станете. И книга не такая уж и большая, читается за 3-4 дня.
  • 0
    Хотя обычно мы говорим «переменные» в Python (потому что это общепризнанная терминология), мы в действительности имеем в виду «имена» или «идентификаторы». В Python «переменные» — это ссылки на значения, а не именованные ячейки.

    Во многих языках есть возможность управлять этим поведением, т.е. передавать переменные либо по ссылке, либо по значению. В питоне переменная содержащая, например, объект типа int по-умолчанию будет передана по значению, а переменная содержащая объект типа list — по ссылке. В данном случае мне не понятны мысли автора о том, что работа с переменными в питоне как-то принципиально отличается в сравнении с другими языками.
    • 0
      Не могу утверждать, но, возможно, автор вскользь говорит о внутренних механизмах интерпретатора Python. Я вижу так, в примере:
      = b = 1
      = 2
      print b
      во второй строке «содается новый объект» типа int и назначается ссылка на него. Да, для пользователя в конечном счете это выглядит как работа с переменными по значению. Но с точки зрения «в Python все имена — объекты» для единообразия нужно считать, что присвоение числа создает новый полноценный объект, доступный по адресу-ссылке, со всеми доступными классу int методами и свойствами.
      Приняв к сведению такую «идиому», можно прийти к выводу, что в Python нет необходимости, как таковой, управлять способом передачи значений: по ссылке или по значению.
    • +2
      > В питоне переменная содержащая, например, объект типа int по-умолчанию будет передана по значению, а переменная содержащая объект типа list — по ссылке.

      Нет. В питоне всё ссылки, даже int. В питоне есть различие только в mutable/immutable'ности объектов…
      • 0
        Да, всё ссылки. Методы неизменяемых объектов (например string'ов) возвращают новые экземпляры этих классов.
      • 0
        Нет. В питоне всё ссылки, даже int.

        Т.е. вы утверждаете, что:
        a = b = 1
        b = 2
        print a

        выведет 2? Если нет, то что это если не передача по значению?
        • 0
          Дабы не вызывать споров о терминах уточню: под «передачей объекта по значению» я понимаю «создание копии исходного объекта и передачи ссылки на него»
        • +1
          Я это не утверждаю. Ваш пример не имеет отношения к «передаче по значению».

          Есть объект int со значением 1 и на него две ссылки «a» и «b». Cледом создается объект int со значением 2 и в «b» помещается ссылка на него.

          Если вы вместо 1 и 2 сделаете список или любой другой объект, то будет тоже самое.
          • +1
            Вы правы. Это я что-то каких-то глупостей понаписал.
          • 0
            Извините, промахнулся, мышой, минус ткнул. Компенсировал кармой.
  • +1
    Полезный пост, спасибо. Я хоть и нуб в питоне, но то что тут описано:
    Значения параметров по умолчанию
    очень не очевидно.
  • +1
    там у вас знак копирайта образовался, попробуйте написать (с )
    • 0
      Исправил, спасибо.
      Еще бы с таблицами придумать чего. В оригинале они без бордеров, атрибут <table border="0"> не очень помог.
      Хоть бы ширину колонок подправить, чтобы картинки постройнее смотрелись.
  • 0
    Спасибо за locals в форматировании строк. Действительно удобно
    • 0
      Не мне, автору))))
  • +1
    «вы можете захотеть использовать» — неудачный англицизм. Лучше «вам может потребоваться», ну или просто «можно использзовать»
  • 0
    > Заметили избыточность?

    Эм, нет. В чем избыточность в использовании values?
    • 0
      Ну как же? Создается промежуточный словарь, в элементы которого копируются значения уже определенных объектов-переменных. Одни и те же значения таким образом дублируются в памяти.
      • 0
        Теперь вижу, спасибо.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.