Pull to refresh

Code Like a Pythonista: Idiomatic Python (part1)

Reading time 9 min
Views 26K
Original author: David Goodger
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__. Просмотр пространства имен на самом деле заключается в поиске по словарю.

Заключительная часть перевода.
Tags:
Hubs:
+59
Comments 23
Comments Comments 23

Articles