Библиотека питониста | Python, Django, Flask
40.8K subscribers
2.68K photos
73 videos
51 files
4.26K links
Все самое полезное для питониста в одном канале.

Список наших каналов: https://t.me/proglibrary/9197

Для обратной связи: @proglibrary_feeedback_bot

По рекламе: @proglib_adv
РКН: https://gosuslugi.ru/snet/67b885cbd501cf3b2cdb5b36
Download Telegram
Некоторые модули могут содержать такие загадочные конструкции:

try:
cache
except NameError:
cache = {}

Похоже, нет смысла делать что-то подобное. Кэш определенно вызывает NameError в начале модуля, так как он не был назначен ранее.

#codeexample

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

#codeexample

for batch in batches(range(9), lambda y: y > 4):
print('[{}]'.format(','.join(
str(x) for x in batch
)))

На выходе:

0,1,2,3,4]
[5,6,7,8]

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

# RuntimeError
list(batches(range(9), lambda y: y > 4))
Функция itertools.chain - это способ перебора многих итерируемых элементов, как если бы они были склеены:

#codeexample

In : list(chain(['a', 'b'], range(3), set('xyz')))
Out: ['a', 'b', 0, 1, 2, 'x', 'z', 'y']

Иногда нужно знать, пуст ли генератор (скажем, исчерпан). Чтобы сделать это, вы должны попытаться получить следующий элемент из генератора. Если это работает, и вы хотите вернуть элемент обратно в генератор, что, конечно, невозможно. Вместо этого вы можете приклеить его обратно цепочкой:

def sum_of_odd(gen):
try:
first = next(gen)
except StopIteration:
raise ValueError('Empty generator')

return sum(
x for x in chain([first], gen)
if x % 2 == 1
)

Пример использования:

In : sum_of_odd(x for x in range(1, 6))
Out: 9
In : sum_of_odd(x for x in range(2, 3))
Out: 0
In : sum_of_odd(x for x in range(2, 2))
...
ValueError: Empty generator
Иногда вы хотите использовать генератор, но вас не волнуют значения, которые он дает. Тем не менее, вам небезразличен какой-либо побочный эффект, это может быть исключение, запись в файл, изменение глобальной переменной и т. д.

Удобный и широко используемый способ сделать это - list (gen ()). Тем не менее, этот код сохраняет все значения в памяти только для того, чтобы сразу же их отбросить. Это может быть нежелательно.

#codeexample

Если вы хотите избежать этого, вы можете использовать deque с ограниченным размером:

from collections import deque

def inversed(nums):
for num in nums:
yield 1 / num

try:
deque(inversed([1, 2, 0]), maxlen=0)
except ZeroDivisionError:
print('E')

Чтобы быть более семантически точным, вам лучше определить свою собственную функцию выпуска:

def exhaust(iterable):
for _ in iterable:
pass
Пишем функцию, которая принимает в качестве аргумента K list и возвращает все возможные списки из K элементов, где первый элемент из первого списка, второй -  из второго и т.д.

#codeexample

assert combinations([1, 2], [3, 4]) == [
    [1, 3],
    [1, 4],
    [2, 3],
    [2, 4],
]
#codeexample

Стандартный модуль json имеет интерфейс командной строки, который может быть полезен для предварительного преобразования JSON одним только Python. Модуль для этого называется json.tool и должен выгдядить так:

$ echo '{"a": [], "b": "c"}' | python -m json.tool
{
"a": [],
"b": "c"
}
Когда вы пишете пользовательский __repr__ для какого-либо объекта, вы обычно хотите включить представление его атрибутов. Для этого вы должны выполнить форматирование вызова repr() для объектов, так как он вызывает str() по умолчанию.

#codeexample

Это делается с помощью !r :

class Pair:
def __init__(self, left, right):
self.left = left
self.right = right

def __repr__(self):
class_name = type(self).__name__
return f'{class_name}({self.left!r}, {self.right!r})'
Если вы хотите перебрать несколько итераций одновременно, вы можете использовать функцию zip (она не имеет ничего общего с форматом файла ZIP):

#codeexample

from datetime import timedelta

names = [
    'Eleven. Return and Revert',
    'Wilderness',
    'The Menagerie Inside',
    'Evaporate',
]

years = [
    2010,
    2013,
    2015,
    2018,
]

durations = [
    timedelta(minutes=57, seconds=38),
    timedelta(minutes=48, seconds=5),
    timedelta(minutes=46, seconds=34),
    timedelta(minutes=43, seconds=25),
]

print('Midas Fall LPs:')
for name, year, duration in zip(
    names, years, durations
):
    print(f'  * {name} ({year}) — {duration}')

Output:

Midas Fall LPs:
  * Eleven. Return and Revert (2010) — 0:57:38
  * Wilderness (2013) — 0:48:05
  * The Menagerie Inside (2015) — 0:46:34
  * Evaporate (2018) — 0:43:25
#codeexample

Если вы хотите создать словарь из известного набора ключей и некоторого фиксированного значения для всех из них, вы можете использовать словарные выражения:

>>> keys = ['a', 'b', 'c']
>>> {k: True for k in keys}
{'a': True, 'b': True, 'c': True}

Однако, у класса dict есть метод класса fromkeys, разработанный специально для этого случая:

>>> dict.fromkeys(keys, True)
{'a': True, 'b': True, 'c': True}

Вы можете добавлять символы Юникода в строковый литерал не только по его номеру, но и по его имени.

>>> '\N{EM DASH}'
'—'
>>> '\u2014'
'—'

Это также совместимо с f-strings:

>>> width = 800
>>> f'Width \N{EM DASH} {width}'
'Width — 800'
#codeexample

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

>>> x = 42
>>> f'{x:5}+{x:15f}'
' 42+ 42.000000'

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

def print_table(matrix):
cols_width = [
max(len(str(row[col])) for row in matrix)
for col in range(len(matrix[0]))
]

for row in matrix:
for i, cell in enumerate(row):
print(
f'{cell:{cols_width[i]}} ',
end=''
)
print()

albums = [
['Eleven. Return and Revert', 2010],
['Wilderness', 2013],
['The Menagerie Inside', 2015],
['Evaporate', 2018],
]

print_table(albums)

На выходе:

Eleven. Return and Revert 2010
Wilderness 2013
The Menagerie Inside 2015
Evaporate 2018
__init__ позволяет изменить объект сразу после создания. Если вы хотите контролировать то, что уже создано, используйте __new__:

#codeexample

from typing import Tuple, Dict
from cached_property import cached_property

class Numbers:
_LOADED: Dict[Tuple[int, ...], 'Numbers'] = {}

def __new__(cls, ints: Tuple[int, ...]):
if ints not in cls._LOADED:
obj = super().__new__(cls)
cls._LOADED[ints] = obj

return cls._LOADED[ints]

def __init__(self, ints: Tuple[int, ...]):
self._ints = ints

@cached_property
def biggest(self):
print('calculating...')
return max(self._ints)

print(Numbers((4, 3, 5)).biggest)
print(Numbers((4, 3, 5)).biggest)
print(Numbers((4, 3, 6)).biggest)
#codeexample

`complex`- это встроенный тип Python для комплексных чисел:

>>> complex(1, 2).real
1.0
>>> abs(complex(3, 4))
5.0
>>> complex(1, 2) == complex(1, -2).conjugate()
True
>>> str(complex(2, -3))
'(2-3j)'

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

>>> (3 + 4j).imag
4.0
>>> not (3 + 4j)
False
>>> (-3 - 4j) + (2 - 2j)
(-1-6j)
Вы можете перевести или удалить символы строки (как это делает утилита tr) с помощью метода перевода str:

#codeexample

>>> 'Hello, world!'.translate({
... ord(','): ';',
... ord('o'): '0',
... })
'Hell0; w0rld!'

Единственный аргумент перевода - это словарь, отображающий коды символов на символы (или коды). Обычно такой словарь удобнее создавать статическим методом str.maketrans:

>>> 'Hello, world!'.translate(str.maketrans({
... ',': ';',
... 'o': '0',
... }))
'Hell0; w0rld!'

Or even:

>>> 'Hello, world!'.translate(str.maketrans(
... ',o', ';0'
... ))
'Hell0; w0rld!'

Третий аргумент для удаления параметров:

>>> tr = str.maketrans(',o', ';0', '!')
>>> tr
{44: 59, 111: 48, 33: None}
>>> 'Hello, world!'.translate(tr)
'Hell0; w0rld'