Трюки и советы по Python, которые облегчат вашу жизнь

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


2018-03-27 13:10

разработка по

Python – это мощный язык общего назначения. Давайте погрузимся в советы по Python, а также изучение трюков, потоков и библиотек.

Введение

Python и его библиотеки используются для автоматизации систем, для написания веб-приложений, а также в отраслях Big Data, аналитики и софтверной безопасности. Эта статья призвана показать малоизвестные советы по Python, чтобы наставить вас на путь быстрой разработки, более легкой отладки и общего удовольствия.

Как и в случае с любым языком, который вы изучаете, получаемый вами реальный ресурс – это не языковая мощь, а возможность использовать идиомы, библиотеки и общие знания Python-комьюнити.

Изучение стандартных типов данных

1. collections.namedtuple

Когда вам не нужно добавлять методы к классу, но все же вы хотите удобства foo.prop, пробуйте использовать подобие кортежа namedtuple. Вы заранее определяете поля, а затем можете создавать легковесный класс, который занимает меньше памяти, чем полный объект.

Python

1

2

3

4

5

LightObject=namedtuple('LightObject',['shortname','otherprop'])

m=LightObject()

m.shortname='athing'

>Traceback(most recent call last):

>AttributeError:can'tsetattribute

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

Python

1

2

3

LightObject=namedtuple('LightObject',['shortname','otherprop'])

n=LightObject(shortname='something',otherprop='something else')

n.shortname# something

2. collections.defaultdict

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

Python

1

2

3

4

5

6

login_times={}

fortinlogins:

iflogin_times.get(t.username,None):

login_times[t.username].append(t.datetime)

else:

login_times[t.username]=[t.datetime]

Выйдем за рамки того, что предлагает defaultdict, и установим вложенные ключи в качестве атрибутов.

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

normal_dict={

'a':{

'b':{

'c':{

'd':{

'e':'really really nested dict'

}

}

}

}

}

fromaddict importDict

addicted=Dict()

addicted.a.b.c.d.e='really really nested'

print(addicted)

# {'a': {'b': {'c': {'d': {'e': 'really really nested'}}}}}

С этим сниппетом будет проще писать, чем со стандартным dict. Еще легче только с defaultdict:

Python

1

2

3

fromcollectionsimportdefaultdict

default=defaultdict(dict)

default['a']['b']['c']['d']['e']='really really nested dict'# fails

Кажется, что выглядит нормально, но на самом деле будет генерироваться исключение KeyError, потому что default[‘a’] – это dict, а не defaultdict. Нужно сделать defaultdict, который по умолчанию использует дефолтные словари.

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

3. Перечисление

Итерация по любому содержимому в Python проста (как и в любом другом языке) – простой цикл for:

Python

1

2

3

4

5

6

7

drinks=["coffee","tea","milk","water"]

fordrink indrinks:

print("thirsty for",drink)

#thirsty for coffee

#thirsty for tea

#thirsty for milk

#thirsty for water

Очень часто требуются одновременно индекс элемента, и сам элемент. Программисты используют len() и range() для перебора списка по индексу, но есть более простой способ.

Python

1

2

3

4

5

6

7

drinks=["coffee","tea","milk","water"]

forindex,drink inenumerate(drinks):

print("Item {} is {}".format(index,drink))

#Item 0 is coffee

#Item 1 is tea

#Item 2 is milk

#Item 3 is water

Функция перечисления возвращает, как индекс, так и элемент.

Управление потоком

Управляющие конструкции – это for, while, if-elif-else и try-except. Советы по Python и правильное использование этих структур помогут в большинстве случаев.

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

1. Исключения

Исключения в качестве управления потоком – это общий шаблон при работе с базами данных, сокетами, файлами или любым ресурсом, который является потенциально опасным. При использовании стандартных try и catch такая простая задача, как работа с базой данных, может превратиться в проблему:

Python

1

2

3

4

5

6

7

8

9

10

11

12

try:

# get API data

data=db.find(id='foo')# may raise exception

# manipulate the data

db.add(data)

# save it again

db.commit()# may raise exception

exceptException:

# log the failure

db.rollback()

db.close()

Это определенно не то, что мы хотим, потому что исключение сработает еще до начала транзакции. Откат также не является верным ответом на отказ соединения, поэтому давайте разберем эти случаи.

Сначала мы рассмотрим данные:

Python

1

2

3

4

5

6

7

8

9

10

try:

# get API data

data=db.find(id='foo')# may raise exception

exceptException:

# log the failure and bail out

log.warn("Could not retrieve FOO")

return

# manipulate the data

db.add(data)

Теперь давайте используем советы по Python и изящно обернем commit:

Python

1

2

3

4

5

6

7

8

9

try:

db.commit()# may raise exception

exceptException:

log.warn("Failure committing transaction, rolling back")

db.rollback()

else:

log.info("Saved the new FOO")

finally:

db.close()

Условие finally дает понять, что db.close() будет выполняться всегда. Оглядываясь назад, мы видим, что весь код, связанный с сохранением наших данных, оказался в хорошей логической группировке на том же уровне отступов. Редактируя этот код позже, нам будет легко увидеть, что все строки привязаны к commit.

2. Контекст и контроль

Ранее мы видели управление потоком с использованием исключений. В общем случае алгоритм выглядит следующим образом:

  • Попытка получить ресурс (файл, сетевое соединение, что угодно).
  • Если это не удается, очистить все, что осталось.
  • В противном случае выполнить действия на ресурсе.
  • Записать что произошло.
  • Программа завершена.

Имея это в виду, давайте рассмотрим еще некоторые советы по Python и второй пример с базами данных из последнего раздела. Мы используем связку try-except-finally, чтобы убедиться, что любая транзакция, которую мы начали, была либо закоммичена, либо отменена.

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

try:

# attempt to acquire a resource

db.commit()

exceptException:

# If it fails, clean up anything left behind

log.warn("Failure committing transaction, rolling back")

db.rollback()

else:

# If it works, perform actions

# In this case, we just log success

log.info("Saved the new FOO")

finally:

# Clean up

db.close()

# Program complete

Наш предыдущий пример повторяет по шагам описанный выше алгоритм. Но как часто эта логика меняется? Не очень часто.

Python

1

2

3

4

5

6

7

8

db=db_library.connect("fakesql://")

# as a function

commit_or_rollback(db)

# context manager

withtransaction("fakesql://")asdb:

# retrieve data here

# modify data here

2.1 Менеджер контекста

Менеджер контекста упрощает защиту некоторого блока, настраивая ресурсы (контекст), необходимые блоку во время выполнения. В нашем примере нам нужна транзакция базы данных, которая будет:

  • Подключаться к БД.
  • Начинаться в начале блока.
  • Коммитить или откатывать в конце блока.
  • Очищать в конце блока.

Метод __enter __() очень простой, поэтому начнем с него.

Python

1

2

3

4

5

6

classDatabaseTransaction(object):

def__init__(self,connection_info):

self.conn=db_library.connect(connection_info)

def__enter__(self):

returnself.conn

Метод __enter __ практически ничего не делает, только возвращает подключение к базе, которое мы можем использовать внутри блока для извлечения или сохранения данных.

Метод __init__  – это то место, где выполняется соединение с БД, и, если он не работает, блок вообще запускаться не будет.

2.2 Работа методов

Теперь рассмотрим, как транзакция будет завершаться в методе __exit __. Этот метод имеет больше обязанностей, т. к. он должен обрабатывать любые исключения в блоке и закрывать транзакцию.

Python

1

2

3

4

5

6

7

8

9

10

def__exit__(self,exc_type,exc_val,exc_tb):

ifexc_type isnotNone:

self.conn.rollback()

try:

self.conn.commit()

exceptException:

self.conn.rollback()

finally:

self.conn.close()

Теперь мы можем использовать нашу DatabaseTransaction как диспетчер контекстов для блока с действиями. В этом блоке будут запускаться методы __enter __  и __exit __, обрабатывать подключение к БД и сбрасывать его, когда мы закончим.

Python

1

2

3

4

# context manager

withDatabaseTransaction("fakesql://")asdb:

# retrieve data here

# modify data here

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

3. Генераторы

Представленные в Python 2 генераторы – это следующие советы по Python и простой способ реализовать итератор, который не хранит сразу все его значения. Обычно функция в Python начинает свое выполнение, выполняет некоторые операции и возвращает результат (или ничего).

Генераторы бывают разные.

Python

1

2

3

4

5

6

7

defmy_generator(v):

yield'first '+v

yield'second '+v

yield'third '+v

print(my_generator('thing'))

# <generator object my_generator at 0x....>

3.1 Ключевые слова

Вместо возврата мы используем yield-слова, что делает генератор особенным. При вызове my_generator(‘thing’) вместо получения результата функции мы получаем объект генератора, который можно использовать везде, где вы могли бы использовать список или другой итератор.

Чаще всего вы можете использовать генераторы в виде части цикла, как показано ниже. Цикл будет продолжаться до тех пор, пока генератор не перестанет давать yield-значения.

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

forvalue inmy_generator('thing'):

printvalue

# first thing

# second thing

# third thing

gen=my_generator('thing')

next(gen)

# 'first thing'

next(gen)

# 'second thing'

next(gen)

# 'third thing'

next(gen)

# raises StopIteration exception

После создания экземпляра генератор ничего не делает, пока у него не попросят параметр. Он выполняется до первого yield и передает это значение обратившемуся, а затем ждет, сохраняя свое состояние, пока не будет запрошено другое значение.

3.2 Генератор Фибоначчи

Теперь давайте создадим генератор, который немного полезнее, чем просто возврат трех жестко закодированных элементов. Пример классического генератора – бесконечный генератор Фибоначчи, который мы попробуем сделать. Он начнется с 1 и возвращает сумму двух предыдущих чисел столько раз, сколько вы попросите.

Python

1

2

3

4

5

6

deffib_generator():

a=0

b=1

whileTrue:

yielda

a,b=b,a+b

Нужно быть осторожными с конечным условием, когда используем генератор т. к.  он “зациклится” и будет бесконечно добавлять значения.

Теперь давайте используем наш генератор, чтобы посчитать первое число Фибоначчи, которое превышает 10 000.

Python

1

2

3

4

5

min=10000

fornumber infib_generator():

ifnumber>min:

print(number,"is the first fibonacci number over",min)

break

Это было довольно просто, и мы можем сделать это число настолько большим, насколько захотим, и код все равно найдет первое число, большее X в последовательности Фибоначчи.

3.3 Пример с API

Теперь попробуем более реальный пример. В API пагинации есть распространенная практика ограничения и предотвращения отправки более 50 мегабайт при помощи JSON на мобильное устройство. Сначала мы объявим API, с которой работаем, а потом напишем генератор.

API, которое мы используем, называется Scream – это сервис, где пользователи могут обсуждать и оставлять отзывы о ресторанах, в которых они были. Оно выглядит так:

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

GET http://scream-about-food.com/search?q=coffee

{

"results":[

{"name":"Coffee Spot",

"screams":99

},

{"name":"Corner Coffee",

"screams":403

},

{"name":"Coffee Moose",

"screams":31

},

{...}

]

"more":true,

"_next":"http://scream-about-food.com/search?q=coffee?p=2"

}

Разработчики ввели ссылку на следующую страницу в ответе API, поэтому будет очень легко получить следующую страницу. Мы также можем покинуть эту страницу и перейти на первую. Чтобы получить данные, мы будем использовать библиотеку запросов в нашем генераторе для отображения результатов поиска.

Генератор имеет ограниченную логику и обрабатывает пагинацию примерно так:

  • Получает поисковой запрос.
  • Запрашивает API-интерфейс scream-about-food.
  • Повторяет попытку, если API не работает.
  • Получает результаты со страницы.
  • Получает следующую страницу, если есть.
  • Выходит, когда результатов больше нет.

Достаточно легко. Для начала мы будем использовать генератор без повторных попыток, чтобы код оставался простым.

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

importrequests

api_url="http://scream-about-food.com/search?q={term}"

definfinite_search(term):

url=api_url.format(term)

whileTrue:

data=requests.get(url).json()

forplace indata['results']:

yieldplace

# end if we've gone through all the results

ifnotdata['more']:break

url=data['_next']

После того, как генератор создан, нужно получить условия поиска, а генератор будет строить запрос и показывать результат. Код, конечно, грубый – исключения не обрабатываются вообще, а если API не отвечает или пришел неизвестный JSON-ответ, то генератор вызовет исключение.

Несмотря на эти огрехи, мы можем использовать генератор для поиска номера позиции в поисковой выдаче нашего ресторана по поисковому запросу “кофе”.

Python

1

2

3

4

5

6

7

# pass a number to start at as the second argument if you don't want

# zero-indexing

fornumber,result inenumerate(infinite_search("coffee"),1):

ifresult['name']=="The Coffee Stain":

print("Our restaurant, The Coffee Stain is number ",number)

return

print("Our restaurant, The Coffee Stain didnt't show up at all! :(")

Генератор обрабатывает итерации на каждой странице поискового результата. Все, что нам нужно, – использовать встроенное перечисление, описанное ранее в статье, чтобы отслеживать количество результатов и выводить их.

В качестве упражнения добавим счетчик к бесконечному поиску infinite_search:

Python

1

2

3

4

5

forresult ininfinite_search("coffee"):

ifresult['name']=="The Coffee Stain":

print("Our restaurant, The Coffee Stain is number ",result['number'])

return

print("Our restaurant, The Coffee Stain didn't show up at all! :(")

Если вы пишете на Python 3, то уже применяли генераторы при использовании стандартной библиотеки. Вызовы вроде dict.items() теперь возвращают генераторы вместо списков. Чтобы получить это поведение в Python 2, был добавлен dict.iteritems(), но он используется не так часто.

Совместимость Python 2 и 3

Переход от Python 2 к Python 3 может быть осуществлен для любого кода, но можно писать код, который работает в обеих версиях. Поддержку Python 2.7 продлили до 2020 года, но маловероятно, что многие новые функции будут поддерживаться. На данный момент рекомендуется писать код с поддержкой Python 2.7 и 3+, если только вы не сможете полностью отказаться от поддержки Python 2.

Подробное руководство по поддержке обеих версий смотрите в гайде на python.org.

Продолжим разбирать  советы по Python и рассмотрим наиболее распространенные вещи, с которыми вы могли столкнуться при попытке написать совместимый код, и разберемся, как использовать __future__ для их работы.

1. print или print()

Почти каждый разработчик, который переключился с Python 2 на 3, использовал неверный оператор печати. К счастью, вы можете стандартизировать использование print как функции (в стиле Python 3): просто импортируем print_function.

Python

1

2

3

4

5

6

print"hello"# Python 2

print("hello")# Python 3

from__future__importprint_function

print("hello")# Python 2

print("hello")# Python 3

2. Деление

По умолчанию поведение деления также изменилось в версии 3. В Python 2 при делении целых чисел будет выполняться целочисленное деление с отбрасыванием десятичных чисел после точки. Это были не совсем те советы по python, которые ожидало большинство пользователей, поэтому в Python 3 изменилось поведение чисел с плавающей точкой.

Python

1

2

3

4

5

6

print(1/3)# Python 2

# 0

print(1/3)# Python 3

# 0.3333333333333333

print(1//3)# Python 3

# 0

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

Python

1

2

3

4

5

6

7

8

9

from__future__importdivision

print(1/3)# Python 2

# 0.3333333333333333

print(1//3)# Python 2

# 0

print(1/3)# Python 3

# 0.3333333333333333

print(1//3)# Python 3

# 0

Оригинал


Источник: proglib.io

Комментарии: