Оригинальный портал:Краткое изложение некоторых подводных камней и приемов в Python
Python — мощный и широко используемый язык, давайте углубимся в него и изучим некоторые трюки с управляющими операторами, трюки со стандартной библиотекой и некоторые распространенные ловушки.
Python (и его различные библиотеки) огромен. Он используется в автоматизации систем, веб-приложениях, больших данных, анализе данных и программном обеспечении для обеспечения безопасности. Этот документ призван показать некоторые малоизвестные советы, которые помогут вам ускорить разработку, упростить отладку и получить больше удовольствия.
Изучение Python, как и изучение всех других языков, действительно полезным ресурсом является не громоздкая и объемная официальная документация каждого языка, а возможность использовать общий синтаксис, библиотеки и сообщество Python для обмена знаниями.
**Исследуйте стандартные типы данных
скромно перечислить**
Обход в Python очень прост, просто используйте «for foo in bar:».
drinks = ["coffee", "tea", "milk", "water"]
for drink in drinks:
print("thirsty for", drink)
#thirsty for coffee
#thirsty for tea
#thirsty for milk
#thirsty for water
Но также общим требованием является использование как порядкового номера элемента, так и самого элемента. Мы часто видим, как некоторые программисты используют len() и range() для перебора списков путем индексации, но есть более простой способ.
drinks = ["coffee", "tea", "milk", "water"]
for index, drink in enumerate(drinks):
print("Item {} is {}".format(index, drink))
#Item 0 is coffee
#Item 1 is tea
#Item 2 is milk
#Item 3 is water
Функция перечисления может одновременно перебирать элементы и их номера.
Тип набора
Многие концепции можно свести к операциям над множествами. Например: убедитесь, что в списке нет повторяющихся элементов, просмотрите элементы, общие для обоих списков и т. д. Python предоставляет тип данных set, чтобы сделать подобные операции более быстрыми и читабельными.
# deduplicate a list *fast*
print(set(["ham", "eggs", "bacon", "ham"]))
# {'bacon', 'eggs', 'ham'}
# compare lists to find differences/similarities
# {} without "key":"value" pairs makes a set
menu = {"pancakes", "ham", "eggs", "bacon"}
new_menu = {"coffee", "ham", "eggs", "bacon", "bagels"}
new_items = new_menu.difference(menu)
print("Try our new", ", ".join(new_items))
# Try our new bagels, coffee
discontinued_items = menu.difference(new_menu)
print("Sorry, we no longer have", ", ".join(discontinued_items))
# Sorry, we no longer have pancakes
old_items = new_menu.intersection(menu)
print("Or get the same old", ", ".join(old_items))
# Or get the same old eggs, bacon, ham
full_menu = new_menu.union(menu)
print("At one time or another, we've served:", ", ".join(full_menu))
# At one time or another, we've served: coffee, ham, pancakes, bagels, bacon, eggs
Функция пересечения сравнивает все элементы в списке и возвращает пересечение двух наборов. В нашем примере основными продуктами для завтрака являются бекон, яйца и ветчина.
collections.namedtuple
Если вы не хотите добавлять методы в класс, но хотите использовать способ вызова foo.prop, тогда все, что вам нужно, это namedtuple. Вы определяете свойства класса заранее, а затем можете создать экземпляр облегченного класса, который занимает меньше памяти, чем полный объект.
LightObject = namedtuple('LightObject', ['shortname', 'otherprop'])
m = LightObject()
m.shortname = 'athing'
> Traceback (most recent call last):
> AttributeError: can't set attribute
Вы не можете устанавливать свойства namedtuple таким образом, так же как вы не можете изменять значение элементов в кортеже. Вам необходимо установить значение свойства при создании экземпляра namedtuple.
LightObject = namedtuple('LightObject', ['shortname', 'otherprop'])
n = LightObject(shortname='something', otherprop='something else')
n.shortname
# something
collections.defaultdict
При написании приложений Python для использования словарей во многих случаях некоторые ключевые слова вообще не существуют, как в следующем примере.
login_times = {}
for t in logins:
if login_times.get(t.username, None):
login_times[t.username].append(t.datetime)
else:
login_times[t.username] = [t.datetime]
Используя defaultdict, мы можем пропустить логику проверки существования ключа, любой доступ к неопределенному ключу вернет пустой список (или другой тип данных).
login_times = collections.defaultdict(list)
for t in logins:
login_times[t.username].append(t.datetime)
Вы даже можете использовать пользовательские классы, которые создают экземпляр класса при вызове.
from datetime import datetime
class Event(object):
def __init__(self, t=None):
if t is None:
self.time = datetime.now()
else:
self.time = t
events = collections.defaultdict(Event)
for e in user_events:
print(events[e.name].time)
Если вы хотите иметь характеристики defaultdict, но также хотите обрабатывать вложенные ключи для доступа к атрибутам, вы можете узнать о наркомане.
normal_dict = {
'a': {
'b': {
'c': {
'd': {
'e': 'really really nested dict'
}
}
}
}
}
from addict import Dict
addicted = Dict()
addicted.a.b.c.d.e = 'really really nested'
print(addicted)
# {'a': {'b': {'c': {'d': {'e': 'really really nested'}}}}}
Эту маленькую программу написать намного проще, чем стандартный диктофон. Так почему бы не использовать defaultdict?Это кажется достаточно простым.
from collections import defaultdict
default = defaultdict(dict)
default['a']['b']['c']['d']['e'] = 'really really nested dict'
# fails
Этот код выглядит нормально, но в итоге выдает исключение KeyError. Это связано с тем, что default['a'] - это словарь, а не defaultdict. Давайте создадим defaultdict, значение которого относится к словарям по умолчанию, которые могут решать только два уровня вложенности.
Если вам просто нужен счетчик по умолчанию, вы можете использовать collection.Counter, этот класс предоставляет множество удобных функций, таких как most_common.
поток управленияПри изучении управляющих структур в Python for, while, if-elif-else и try-except обычно воспринимаются всерьез. При правильном использовании эти несколько управляющих структур могут справиться с большинством ситуаций. По этой причине почти каждый язык, с которым вы сталкиваетесь, предоставляет аналогичные операторы управляющей структуры. В дополнение к основным структурам управления Python также предоставляет некоторые дополнительные структуры управления, которые обычно не используются, что может сделать ваш код более читабельным и удобным для сопровождения.
Great Exceptations
Исключения — это управляющая структура, которая очень часто используется при работе с базами данных, сокетами, файлами или любым ресурсом, который может выйти из строя. При написании операций с базой данных с использованием стандартных конструкций try и exclude обычно используется именно такой способ ввода.
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
except Exception:
# log the failure
db.rollback()
db.close()
Можете ли вы определить проблему здесь? Есть два возможных исключения, которые вызывают один и тот же модуль, кроме модуля. Это означает, что сбой при поиске данных (или установлении соединения с запросом данных) приводит к резервной операции. Это точно не то, что нам нужно, так как на данный момент транзакция еще не началась. Также откат не должен быть правильным ответом на неудачное соединение с базой данных, поэтому давайте разделим случаи.
Сначала мы обработаем данные запроса.
try:
# get API data
data = db.find(id='foo')
# may raise exception
except Exception:
# log the failure and bail out
log.warn("Could not retrieve FOO")
return
# manipulate the data
db.add(data)
Извлечение данных теперь имеет свою собственную попытку, кроме того, поэтому мы можем делать все, что захотим, когда мы не получаем данные. Без данных наш код вряд ли будет делать что-то полезное, поэтому мы просто выйдем из функции. Вместо выхода вы также можете создать объект по умолчанию, повторить поиск или завершить всю программу.
Теперь давайте обернем код фиксации отдельно, чтобы он также более изящно обрабатывал ошибки.
try:
db.commit()
# may raise exception
except Exception:
log.warn("Failure committing transaction, rolling back")
db.rollback()
else:
log.info("Saved the new FOO")
finally:
db.close()
Фактически, мы добавили код для обоих концов. Во-первых, давайте посмотрим на остальное, код здесь выполняется, когда не возникает никаких исключений. В нашем случае это просто логирование успешности транзакции, но при необходимости можно делать и более интересные вещи. Одним из возможных приложений является запуск фоновых задач или уведомлений.
Очевидно, что цель предложения finally здесь — гарантировать, что db.close() всегда будет работать. Оглядываясь назад, мы видим, что весь код, связанный с хранением данных, в конечном итоге оказывается в хорошей логической группе на том же уровне отступов. Когда в будущем потребуется обслуживание кода, будет очень интуитивно понятно, что эти строки кода используются для завершения операции фиксации.
Context and Control
Ранее мы видели использование исключений для обработки потока управления. Как правило, основные шаги следующие:
尝试获取资源(文件、网络连接等)
如果失败,清除留下的所有东西
成功获得资源则进行相应操作
写日志
程序结束
Имея это в виду, давайте еще раз взглянем на пример с базой данных из предыдущей главы. Мы используем try-except-finally, чтобы гарантировать, что любая транзакция, которую мы запускаем, будет либо зафиксирована, либо отброшена.
try:
# attempt to acquire a resource
db.commit()
except Exception:
# 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
Наш предыдущий пример почти точно соответствует только что упомянутым шагам. Эта логика сильно изменилась? Немного.
Мы будем делать одни и те же шаги почти каждый раз, когда будем сохранять данные. Мы можем записать эту логику в метод или использовать диспетчер контекста.
db = db_library.connect("fakesql://")
# as a function
commit_or_rollback(db)
# context manager
with transaction("fakesql://") as db:
# retrieve data here
# modify data here
Менеджер контекста защищает сегмент кода, устанавливая ресурсы (контексты), необходимые для запуска сегмента кода. В нашем случае нам нужно обработать транзакцию базы данных, тогда процесс будет таким:
连接数据库
在代码段的开头开始操作
在代码段的结尾提交或者回滚
在代码段的结尾清除资源
Давайте создадим менеджер контекста и будем использовать его для настройки нашей скрытой базы данных. Интерфейс contextmanager очень прост. Объект диспетчера контекста должен иметь метод __enter__() для установки желаемого контекста и метод __exit__(exc_type, exc_val, exc_tb), вызываемый после выхода из сегмента кода. Если исключений нет, то все три аргумента exc_* будут равны None.
Метод __enter__ здесь очень простой, начнем с этой функции.
class DatabaseTransaction(object):
def __init__(self, connection_info):
self.conn = db_library.connect(connection_info)
def __enter__(self):
return self.conn
Метод enter__ просто возвращает соединение с базой данных, которое мы используем для доступа к данным во фрагменте кода. Соединение с базой данных на самом деле находится в __initметод, поэтому в случае сбоя подключения к базе данных сегмент кода не будет выполняться.
Теперь давайте определим, как будет проходить транзакция.exitметод завершен. Здесь предстоит проделать больше работы, потому что здесь обрабатываются все исключения в сегменте кода, а также завершается закрытие транзакции.
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
self.conn.rollback()
try:
self.conn.commit()
except Exception:
self.conn.rollback()
finally:
self.conn.close()
Теперь мы можем использовать класс DatabaseTransaction в качестве менеджера контекста для нашего примера. Внутри класса,enterиexitМетод запустится и установит соединение для передачи данных и обработает последствия.
# context manager
with DatabaseTransaction("fakesql://") as db:
# retrieve data here
# modify data here
Чтобы улучшить наш (простой) менеджер транзакций, мы можем добавить различную обработку исключений. Как и сейчас, этот менеджер транзакций скрыл от нас много сложной обработки, поэтому вам не нужно беспокоиться о деталях, связанных с базой данных, каждый раз, когда вы извлекаете данные из базы данных.
Строитель
Генераторы, представленные в Python 2, — это простой способ реализовать итерацию без создания всех значений сразу. Типичное поведение функции в Python — начать выполнение, затем выполнить какую-то операцию и, наконец, вернуть результат (или нет).
Генераторы ведут себя по-разному.
def my_generator(v):
yield 'first ' + v
yield 'second ' + v
yield 'third ' + v
print(my_generator('thing'))
#
Используйте ключевое слово yield вместо return , это то, что делает генераторы уникальными. Когда мы вызываем my_generator('thing'), я получаю не результат функции, а объект генератора, который можно использовать везде, где мы используем список или другие итерации.
Более распространено использование генераторов как части цикла, как в примере ниже. Цикл будет продолжаться до тех пор, пока генератор не перестанет выдавать значения.
for value in my_generator('thing'):
print value
# 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
После создания экземпляра генератора он ничего не делает, пока его не попросят сгенерировать значение, после чего он будет выполняться до тех пор, пока не встретит первый выход и не вернет это значение вызывающей стороне, затем генератор сохраняет контекст и зависает до тех пор, пока вызывающая сторона требуется следующее значение.
Теперь давайте напишем более полезный генератор, чем просто возвращающий три жестко заданных значения. Классический пример генератора — генератор бесконечной последовательности Фибоначчи, давайте попробуем. Последовательность начинается с 1 и по очереди возвращает сумму первых двух чисел.
def fib_generator():
a = 0
b = 1
while True:
yield a
a, b = b, a + b
Обычно следует избегать цикла while True в функции, так как он приведет к тому, что функция не сможет вернуться, но для генераторов это не имеет значения, если цикл имеет yield . Мы должны обратить внимание на добавление конечных условий при использовании этого генератора, потому что генератор может постоянно возвращать значения.
Теперь используйте наш генератор для вычисления первого значения Фибоначчи больше 10000.
min = 10000
for number in fib_generator():
if number > min:
print(number, "is the first fibonacci number over", min)
break
Это очень просто, мы можем сделать значение сколь угодно большим, и код в конечном итоге выдаст первое значение в последовательности Фибоначчи, большее, чем X.
Давайте рассмотрим более практический пример. Интерфейсы с перелистыванием страниц — это распространенный способ справиться с ограничениями приложений и избежать отправки пакетов JSON размером более 50 мегабайт на мобильные устройства. Сначала мы определяем нужный нам API, затем пишем для него генератор, чтобы скрыть логику перелистывания страниц в нашем коде.
Используемый нами API взят из Scream, места, где пользователи обсуждают рестораны, в которых они ели или хотят поесть. Их поисковый API очень прост и в основном выглядит так.
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, чтобы, когда нужно получить следующую страницу, это было очень просто. Мы можем игнорировать номер страницы и просто получить первую страницу. Чтобы получить данные, мы будем использовать библиотеку общих запросов и обернем ее генератором для отображения результатов поиска.
Этот генератор будет обрабатывать разбивку на страницы и ограничивать логику повторных попыток, он будет работать следующим образом:
收到要搜索的内容
查询scream-about-food接口
如果接口失败进行重试
一次yield一个结果
如果有的话,获取下一页
当没有更多结果时,退出
очень простой. Я буду реализовывать этот генератор, а логику повторов мы пока рассматривать не будем для упрощения кода.
import requests
api_url = "http://scream-about-food.com/search?q={term}"
def infinite_search(term):
url = api_url.format(term)
while True:
data = requests.get(url).json()
for place in data['results']:
yield place
# end if we've gone through all the results
if not data['more']: break
url = data['_next']
Когда мы создаем генератор, вы просто передаете содержимое поиска, а генератор сгенерирует запрос и получит результат, если он существует. Конечно, здесь есть некоторые нерешенные пограничные вопросы. Исключение не обрабатывается, генератор выдает исключение, когда API дает сбой или возвращает нераспознанный JSON.
Несмотря на эти несовершенства, мы все же смогли использовать эти коды, чтобы наш ресторан занял место в результатах поиска по ключевому слову «кофе».
# pass a number to start at as the second argument if you don't want
# zero-indexing
for number, result in enumerate(infinite_search("coffee"), 1):
if result['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! :(")
Если вы используете Python 3, вы также можете использовать генераторы при использовании стандартной библиотеки. При вызове таких функций, как dict.items() , вместо возврата списка возвращается генератор. Чтобы получить такое поведение в Python 2, в Python 2 была добавлена функция dict.iteritems(), но она используется реже.
Совместимость с Python 2 и 3
Переход с Python 2 на Python 3 — сложная задача для любой кодовой базы (или разработчика), но можно написать код, который работает с обеими версиями. Python 2.7 будет поддерживаться до 2020 года, но многие новые функции не будут поддерживаться для обеспечения обратной совместимости. На данный момент, если вы не можете полностью отказаться от Python 2, лучше всего использовать функции, совместимые с Python 2.7 и 3+.
Подробное руководство по функциям, поддерживаемым обеими версиями, см. в разделе Портирование кода Python 2 на сайте python.org.
Давайте рассмотрим наиболее распространенные ситуации, с которыми вы столкнетесь при попытке написать совместимый код, и как их использовать.futureкак обходной путь.
print or print()
Почти каждый разработчик, который переходит с Python 2 на Python 3, пишет неправильные выражения для печати. К счастью, вы можете написать совместимые отпечатки, импортировав модуль print_function и рассматривая print как функцию, а не как ключевое слово.
for result in infinite_search("coffee"):
if result['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! :(")
Divided Over Division
Поведение деления по умолчанию также изменилось с Python 2 на Python 3. В Python 2 при делении целых чисел выполняется только целочисленное деление, а дробная часть усекается. Большинству пользователей такое поведение не нравится, поэтому в Python 3 даже деление целых чисел выполняет деление с плавающей запятой.
print "hello"
# Python 2
print("hello")
# Python 3
from __future__ import print_function
print("hello")
# Python 2
print("hello")
# Python 3
Это изменение в поведении может привести к большому количеству мелких ошибок при написании кода, работающего как на Python 2, так и на Python 3. нам нужно сноваfutureмодуль. Импорт подразделения заставит код работать одинаково в обеих версиях.
print(1 / 3)
# Python 2
# 0
print(1 / 3)
# Python 3
# 0.3333333333333333
print(1 // 3)
# Python 3
# 0
Для получения более интересного контента, пожалуйста, следуйтеЭкстремальный интеллект - профессиональное сообщество искусственного интеллекта
Чрезвычайно умныйЭто сообщество, которое помогает разработчикам ИИ расти. Здесь вы можете изучать и делиться с другими знаниями и технологиями, связанными с ИИ, включая языки программирования, математические основы, машинное обучение и связанные с ним технические основы. В настоящее время сообщество в основном предоставляет такие функции, как обмен статьями, обсуждение тем и совместное использование учебных ресурсов, охватывающих машинное обучение, глубокое обучение, обработку естественного языка, распознавание речи, машинное зрение, робототехнику и другие области.