TGViewer
Channel Public Channel
Библиотека Python разработчика | Книги по питону

Библиотека Python разработчика | Книги по питону

@bookpython

Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍

По всем вопросам @evgenycarter

РКН clck.ru/3Ko7Hq
Subscribers
18.1K
Photos
1.1K
Videos
403
Links
1.2K

Showing posts older than #3738 · Back to latest

Older Posts 20 shown
Post #3737 2.63K
Стандартный модуль json имеет интерфейс командной строки, который может быть полезен для форматирования JSON только с помощью Python. Модуль для этого называется json.tool и предназначен для вызова следующим образом:


$ echo '{"a": [], "b": "c"}' | python -m json.tool
{
"a": [],
"b": "c"
}


👉@BookPython
  • 👍 5
  • ❤ 1
Post #3735 2.82K
Чтобы немедленно остановить выполнение программы на Python, следует использовать sys.exit(). Альтернативой является функция exit(), однако она предназначена для использования в интерактивном режиме. Благодаря строковому представлению, она может помочь пользователям, которые пытаются завершить сессию, используя exit (что поддерживается многими оболочками):


>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit
>>> str(exit)
'Use exit() or Ctrl-D (i.e. EOF) to exit'


И exit(), и sys.exit() на самом деле не завершают программу, а просто выбрасывают исключение SystemExit. SystemExit — это прямой подкласс BaseException, а значит, он не может быть перехвачен через except Exception, но может быть перехвачен через except BaseException или через голый except:.


>>> try:
... exit()
... except:
... 'Nothing'
...
'Nothing'


Поскольку это может быть проблемой, можно использовать функцию os._exit. Она не выбрасывает никаких исключений — просто завершает текущий процесс. Однако это означает, что блоки finally, а также завершающие действия менеджеров контекста не будут выполнены.


$ python3
Python 3.4.3 (default, Apr 28 2015, 13:37:07)
[GCC 4.8.3 20140911 (Red Hat 4.8.3-9)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
... os._exit(42)
... finally:
... print('Bye!')
...
$ ...


👉@BookPython
  • 👍 4
  • ❤ 1
Post #3733 2.8K
Иногда программное обеспечение начинает вести себя странно в продакшене. Вместо того чтобы просто перезапустить его, вы, вероятно, захотите понять, что именно происходит, чтобы позже это исправить.

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

В таком случае может пригодиться strace. Это утилита Unix, которая отслеживает системные вызовы. Вы можете запустить её заранее — strace python script.py — но чаще бывает удобнее подключиться к уже работающему процессу: strace -p PID.


$ cat test.py
with open('/tmp/test', 'w') as f:
f.write('test')
$ strace python test.py 2>&1 | grep open | tail -n 1
open("/tmp/test", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3


Каждая строка в выводе strace содержит имя системного вызова, его аргументы в скобках и возвращаемое значение. Поскольку некоторые аргументы являются выходными параметрами (используются для возврата результата, а не для передачи данных), вывод строки может быть прерван до завершения системного вызова.

В следующем примере вывод приостанавливается до тех пор, пока кто-то не введёт данные в STDIN:


$ strace python -c 'input()'
...
read(0,


👉@BookPython
  • 👍 8
  • ❤ 4
Post #3731 3.45K
У Python очень короткий список встроенных констант. Одна из них — Ellipsis, которую также можно записать как .... Эта константа не имеет особого значения для интерпретатора, но используется в ситуациях, где такой синтаксис уместен.

В библиотеке NumPy Ellipsis поддерживается в качестве аргумента для __getitem__, например, x[...] возвращает все элементы массива x.

PEP 484 придаёт Ellipsis дополнительный смысл: Callable[..., type] используется для обозначения типа вызываемых объектов без указания типов аргументов.

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


def x():
...


👉@BookPython
  • 👍 5
  • ❤ 3
  • 👎 1
Post #3729 3.45K
💡10 функций, для продвинутых Python-разработчиков

1. Разворачиваем вложенных списков любой глубины

flatten = lambda lst: [x for sub in lst for x in (flatten(sub) if isinstance(sub, list) else [sub])]


2. Декоратор для мемоизации результатов функции

memoize = lambda f: (lambda *args, _cache={}, **kwargs: _cache.setdefault((args, tuple(kwargs.items())), f(*args, **kwargs)))


3. Разбиение списка на куски длины n

chunked = lambda lst, n: [lst[i:i+n] for i in range(0, len(lst), n)]


4. Уникализация последовательности с сохранением порядка

uniq = lambda seq: list(dict.fromkeys(seq))


5. Глубокий доступ к вложенным ключам словаря

deep_get = lambda d, *keys: __import__('functools').reduce(lambda a, k: a.get(k) if isinstance(a, dict) else None, keys, d)


6. Преобразование Python-объекта в читаемый JSON

pretty_json = lambda obj: __import__('json').dumps(obj, ensure_ascii=False, indent=2)


7. Чтение последних n строк файла (аналог tail)

tail = lambda f, n=10: list(__import__('collections').deque(open(f), maxlen=n))


8. Выполнение shell-команды и возврат вывода

sh = lambda cmd: __import__('subprocess').run(cmd, shell=True, check=True, capture_output=True).stdout.decode().strip()


9. Быстрое объединение путей

path_join = lambda *p: __import__('os').path.join(*p)


10. Группировка списка словарей по значению ключа

group_by = lambda seq, key: {k: [d for d in seq if d.get(key) == k] for k in set(d.get(key) for d in seq)}


👉@BookPython
  • 👍 6
  • 🤔 5
  • ❤ 1
Post #3727 3.01K
Если вы хотите итерироваться одновременно по нескольким итерируемым объектам, функция zip может быть хорошим выбором. Она возвращает генератор, который выдаёт кортежи, содержащие по одному элементу из каждого исходного итерируемого объекта:


In : eng = ['one', 'two', 'three']
In : ger = ['eins', 'zwei', 'drei']
In : for e, g in zip(eng, ger):
...: print('{e} = {g}'.format(e=e, g=g))
...:


Вывод:


one = eins
two = zwei
three = drei


Обратите внимание, что zip принимает итерируемые объекты как отдельные аргументы, а не в виде списка аргументов. Чтобы «развернуть» значения (unzip), можно использовать оператор *:


In : list(zip(*zip(eng, ger)))
Out: [('one', 'two', 'three'), ('eins', 'zwei', 'drei')]


👉@BookPython
  • 👍 5
Post #3725 2.97K
Иногда в тестах нужно сравнивать сложные структуры, игнорируя некоторые значения. Обычно это делается путем сравнения отдельных значений внутри структуры:


>>> d = dict(a=1, b=2, c=3)
>>> assert d['a'] == 1
>>> assert d['c'] == 3


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


>>> assert d == dict(a=1, b=ANY, c=3)


Это легко реализовать, определив метод __eq__:


>>> class AnyClass:
... def __eq__(self, another):
... return True
...
>>> ANY = AnyClass()


👉@BookPython
  • 👍 5
  • ❤ 3
  • 🥱 2
  • ❤‍🔥 1
  • 👎 1
Post #3723 3.46K
В Python нет оператора ++, вместо него используется x += 1. Однако синтаксис ++x всё ещё допустим (в отличие от x++, который вызывает ошибку).

Суть в том, что в Python есть унарный плюс, и выражение ++x на самом деле интерпретируется как x.__pos__().__pos__(). Этим можно злоупотребить и заставить ++ работать как инкремент (хотя так делать не рекомендуется):


class Number:
def __init__(self, value):
self._value = value

def __pos__(self):
return self._Incrementer(self)

def inc(self):
self._value += 1

def __str__(self):
return str(self._value)

class _Incrementer:
def __init__(self, number):
self._number = number

def __pos__(self):
self._number.inc()


x = Number(4)
print(x) # 4
++x
print(x) # 5


Здесь ++x вызывает дважды __pos__(): сначала на x, затем на возвращённом объекте _Incrementer, в котором второй __pos__() вызывает inc(), увеличивая значение.

👉@BookPython
  • 👍 3
  • ❤ 2
  • 🌚 2
Post #3721 3.35K
Начиная с Python 3.7, модуль contextlib предоставляет декоратор asynccontextmanager, который позволяет определять асинхронные контекстные менеджеры точно так же, как contextmanager:


import asyncio
from contextlib import asynccontextmanager

@asynccontextmanager
async def slow(delay):
half = delay / 2
await asyncio.sleep(half)
yield
await asyncio.sleep(half)

async def main():
async with slow(1):
print('slow')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())


Для более старых версий Python можно использовать @asyncio_extras.async_contextmanager.

👉@BookPython
  • 👍 4
Post #3719 3.3K
Если ты хочешь, чтобы контекстный менеджер при входе или выходе из контекста приостанавливал выполнение корутины, следует использовать асинхронные контекстные менеджеры. Вместо вызова m.__enter__() и m.__exit__() Python в этом случае выполняет await m.__aenter__() и await m.__aexit__() соответственно.

Асинхронные контекстные менеджеры нужно использовать с синтаксисом async with:


import asyncio

class Slow:
def __init__(self, delay):
self._delay = delay

async def __aenter__(self):
await asyncio.sleep(self._delay / 2)

async def __aexit__(self, *exception):
await asyncio.sleep(self._delay / 2)

async def main():
async with Slow(1):
print('slow')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())


В этом примере класс Slow симулирует задержку при входе и выходе из контекста.

👉@BookPython
  • 👍 4
  • ❤ 1
Post #3716 3.43K
Начиная с Python 3.5, стало возможно использовать распаковку в литералах словарей и списков.

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


{**{'a': 1}, 'b': 2, **{'c': 3}}
# Результат: {'a': 1, 'b': 2, 'c': 3}


Пример со списком:


[1, 2, *[3, 4]]
# Результат: [1, 2, 3, 4]


Для словарей такая форма даже мощнее, чем функция dict, потому что позволяет переопределять значения:


{**{'a': 1, 'b': 1}, 'a': 2, **{'b': 3}}
# Результат: {'a': 2, 'b': 3}


👉@BookPython
  • 👍 7
  • ❤ 1
Post #3714 2.99K
Ты можешь создавать словари двумя способами: с помощью литералов или функции dict:


>>> dict(a=1, b=2)
{'a': 1, 'b': 2}
>>> {'a': 1, 'b': 2}
{'a': 1, 'b': 2}


Литералы работают быстрее, чем dict, но у функции есть свои преимущества.

Во-первых, не нужно ставить дополнительные кавычки. Однако это работает только в том случае, если все ключи — допустимые идентификаторы Python:


>>> dict(a=1)
{'a': 1}
>>> dict(1='a')
File "<stdin>", line 1
SyntaxError: keyword can't be an expression


Во-вторых, ты не сможешь случайно указать один и тот же ключ дважды:


>>> {'a': 1, 'a': 1}
{'a': 1}
>>> dict(a=1, a=1)
File "<stdin>", line 1
SyntaxError: keyword argument repeated


В-третьих, легко создать новый словарь на основе уже существующего:


>>> d = dict(b=2)
>>> dict(a=1, **d)
{'a': 1, 'b': 2}


Но учти, что ключи нельзя переопределять при таком способе:


>>> dict(b=3, **d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type object got multiple values for keyword argument


👉@BookPython
  • 👍 5
  • 👏 1
Post #3712 3.11K
Если ты хочешь запустить код с изменённой глобальной переменной, лучше использовать контекстный менеджер, а не менять её напрямую:


from contextlib import contextmanager

QUIT_MESSAGE = 'Bye'

def print_quit_mesage():
global QUIT_MESSAGE
print(QUIT_MESSAGE)

@contextmanager
def global_variable_changed(name, value):
orig_value = globals()[name]
globals()[name] = value
yield
globals()[name] = orig_value

with global_variable_changed(
'QUIT_MESSAGE',
'Tschüss'
):
print_quit_mesage()


👉@BookPython
  • 👍 6
Post #3710 2.86K
Если ты хочешь, чтобы у объектов класса автоматически увеличивался ID, это можно реализовать, отслеживая текущий ID в атрибуте класса:


class Task:
_task_id = 0

def __init__(self):
self._id = self._task_id
type(self)._task_id += 1


Обрати внимание, что нельзя использовать self._task_id += 1 — это создаст атрибут _task_id внутри экземпляра, а не изменит значение на уровне класса.

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


class Task:
_task_id = 0

def __init__(self, task_id):
self._id = task_id

@classmethod
def create(cls):
obj = cls(cls._task_id)
cls._task_id += 1
return obj


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

👉@BookPython
  • 👍 4
  • 👎 1
Post #3709 2.46K
Существует две встроенные функции, которые позволяют анализировать итерируемые объекты без необходимости писать тривиальные и избыточные циклы for. Это all и any.

any возвращает True, если хотя бы одно значение истинно; all возвращает True, только если все значения истинны. Для пустого итерируемого объекта all возвращает True, а any — False.

Обе функции особенно полезны в сочетании со списковыми включениями (list comprehensions):


package_broken = any(
part.is_broken() for part in package.get_parts()
)
package_ok = all(
part.ok() for part in package.get_parts()
)


Функции any и all зачастую взаимозаменяемы благодаря законам де Моргана. Используй ту, с которой код будет понятнее.

👉@BookPython
  • 👍 6
  • 👎 1
Post #3707 2.54K
Иногда нужно создать функцию на основе более универсальной.

Например, у функции int() есть параметр base, который мы хотим зафиксировать, чтобы получить новую функцию base2:


>>> int("10")
10
>>> int("10", 2)
2
>>> def base2(x):
... return int(x, 2)
...
>>> base2("10")
2


functools.partial позволяет сделать то же самое, но точнее и семантически понятнее:


base2 = partial(int, base=2)


Это полезно, когда нужно передать функцию как аргумент другой, более общей функции, но при этом некоторые аргументы должны быть зафиксированы:


>>> map(partial(int, base=2), ["1", "10", "100"])
[1, 2, 4]


Без partial пришлось бы делать так:


>>> map(lambda x: int(x, base=2), ["1", "10", "100"])
[1, 2, 4]


👉@BookPython
  • ❤ 4
  • 👏 1
Post #3706 2.66K
В Python нет поддержки асинхронных операций с файлами. Чтобы сделать их неблокирующими, нужно использовать отдельные потоки.

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

Сторонний модуль aiofiles делает всё это за тебя, предоставляя простой и удобный интерфейс:


async with aiofiles.open('filename', mode='r') as f:
contents = await f.read()


👉@BookPython
  • 👍 9
  • ❤ 1
Post #3704 2.81K
>>> bool(datetime(2018, 1, 1).time())
False
>>> bool(datetime(2018, 1, 1, 13, 12, 11).time())
True


До Python 3.5 объекты datetime.time() считались ложными, если они представляли полночь по UTC. Это могло приводить к трудноуловимым ошибкам. В следующих примерах if not может выполниться не потому, что created_time равен None, а потому, что время — полночь.


def create(created_time=None) -> None:
if not created_time:
created_time = datetime.now().time()


Можно исправить это, явно проверяя на None:


def create(created_time=None) -> None:
if created_time is None:
created_time = datetime.now().time()


👉@BookPython
  • 👍 6
  • 🤔 1
  • 👾 1
Post #3702 2.76K
In:


int('୧৬𝟙༣')


Out:


1613


Цифры 0 1 2 3 4 5 6 7 8 9 — это не единственные символы, которые считаются цифрами. Python следует правилам Unicode и считает «цифрами» сотни различных символов. Вот полный список таких символов.

Это влияет на такие функции, как int, unicode.isdecimal и даже re.match:


# Пример 1
int('෯')
# Вывод:
9

# Пример 2
'٢'.isdecimal()
# Вывод:
True

# Пример 3
import re
bool(re.match(r'\d', '౫'))
# Вывод:
True


👉@BookPython
  • 👍 5
  • 🤔 2
  • 😱 1
Post #3701 2.74K
Начиная с Python 3.0, при возникновении нового исключения внутри блока except перехваченное исключение автоматически сохраняется в атрибуте __context__ создаваемого исключения. В результате при выводе будут показаны оба исключения:


try:
1 / 0
except ZeroDivisionError:
raise ValueError('Zero!')



(most recent call last):
File "test.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "test.py", line 4, in <module>
raise ValueError('Zero!')
ValueError: Zero!


Кроме того, вы можете явно указать причинное исключение, использовав конструкцию raise … from. Тогда в атрибут __cause__ нового исключения будет помещено исходное:


division_error = None

try:
1 / 0
except ZeroDivisionError as e:
division_error = e

raise ValueError('Zero!') from division_error



(most recent call last):
File "test.py", line 4, in <module>
1 / 0
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "test.py", line 8, in <module>
raise ValueError('Zero!') from division_error
ValueError: Zero!


👉@BookPython
  • ❤ 6
  • 👍 2
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →