Определяем наиболее часто встречающиеся элементы в последовательности.
Допустим, у нас есть некоторая последовательность из слов (words), и мы хотим узнать, какие элементы в ней встречаются чаще остальных.
Для этого можно использовать класс
Самому
#std
Допустим, у нас есть некоторая последовательность из слов (words), и мы хотим узнать, какие элементы в ней встречаются чаще остальных.
Для этого можно использовать класс
Counter
из модуля collections
в котором есть метод most_common()
, который и выдаст список элементов, которые встречаются чаще остальных.Самому
Counter
можно скормить любую последовательность элементы которой хешируются. В основе Counter лежит словарь который отображает количество значений, поэтому мы можем узнать количество элементов по ключу:>>>counts['test']
2
Кстати, эта задача часто встречается на собеседованиях и просят её реализовать без модуля collections. Как бы вы её решили? Ответы можете писать в комментарии, обсудим.#std
Ручной проход по списку
Задачка: нужно пройти по списку не используя цикл for.
Решение у этой задачи достаточно простое - можно использовать функцию
Здесь мы получаем итератор - это такой объект, который облегчает навигацию по коллекциям. Дальше в цикле вызываем
#std
Задачка: нужно пройти по списку не используя цикл for.
Решение у этой задачи достаточно простое - можно использовать функцию
next()
и ловить исключение StopIteration
. На скриншоте можно увидеть пример с использованием цикла while.Здесь мы получаем итератор - это такой объект, который облегчает навигацию по коллекциям. Дальше в цикле вызываем
next()
, который получает следующий элемент и так до тех пор пока элементы не закончатся - индикатором этого будет вызов исключения StopIteration
.#std
Я заметил, что редко использую функции из
Сам модуль itertools это набор из эффективных и быстрых по памяти инструментов, возвращающие итераторы. Сами по себе итераторы можно комбинировать с такими функциями как
А сегодня мы начнем c самых простых - c бесконечных итераторов. Всего по документации их три:
Ниже я буду указывать аргументы по умолчанию и возможные передаваемые типы как тайп-хинты. Надеюсь ни у кого не возникнет проблем с пониманием.
#itertools #std
itertools
, в основном когда нужно произвести какие-то "красивые" шаманства. Теперь будем вспоминать саму библиотеку вместе, естественно с примерами :)Сам модуль itertools это набор из эффективных и быстрых по памяти инструментов, возвращающие итераторы. Сами по себе итераторы можно комбинировать с такими функциями как
map()
, list()
лямбдами или же использовать их с помощью цикла for. А сегодня мы начнем c самых простых - c бесконечных итераторов. Всего по документации их три:
count()
, cycle()
и repeat()
.Ниже я буду указывать аргументы по умолчанию и возможные передаваемые типы как тайп-хинты. Надеюсь ни у кого не возникнет проблем с пониманием.
#itertools #std
Коробка с питоном
Я заметил, что редко использую функции из itertools, в основном когда нужно произвести какие-то "красивые" шаманства. Теперь будем вспоминать саму библиотеку вместе, естественно с примерами :) Сам модуль itertools это набор из эффективных и быстрых по памяти…
count(start: int | float = 0, step: int | float = 1) -> Iterator[int | float]
Создает итератор, который возвращает равномерно распределенные значения, начиная с числа, указанного в аргументе start. Само значение-шаг указывается в переменной step:>>> first = count(10, 2)Небольшая загадка для вас - почему вместо 0.3 получили такое число? Пишите в комменты :)
>>> next(first)
10
>>> next(first)
12
>>> next(first)
14
>>> second = count(0.1, 0.1)
>>> next(second)
0.1
>>> next(second)
0.2
>>> next(second)
0.30000000000000004
>>> next(second)
0.4
#itertools #std
Коробка с питоном
Я заметил, что редко использую функции из itertools, в основном когда нужно произвести какие-то "красивые" шаманства. Теперь будем вспоминать саму библиотеку вместе, естественно с примерами :) Сам модуль itertools это набор из эффективных и быстрых по памяти…
cycle(p: Iterable) -> Iterator
Создает итератор, который возвращает элементы из переданного итератора поштучно и сохраняет их. После того как элементы закончились, он начинает возвращать сохранённые элементы и так до тех пор, пока мы сами не прервем его работу: >>> first = cycle([1, 2])Например, вот так при помощи функций islice и cycle мы можем сгенерировать список из 20 элементов из необходимой нам коллекции:
>>> next(first)
1
>>> next(first)
2
>>> next(first)
1
>>> next(first)
2
>>> list(islice(cycle([1, 2, 3, 4]), None, 20))
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
#itertools #std
Коробка с питоном
Я заметил, что редко использую функции из itertools, в основном когда нужно произвести какие-то "красивые" шаманства. Теперь будем вспоминать саму библиотеку вместе, естественно с примерами :) Сам модуль itertools это набор из эффективных и быстрых по памяти…
repeat(object: T, times: Optional[int] = None) -> Iterable[T]
Создает итератор который возвращает объект снова и снова. Можно указать параметр times, который вернет объект заданное количество раз и завершит работу итератора:>>> first = repeat("hehe", 3)Очень часто repeat используют чтобы по быстрому сгенерировать коллекцию из элементов:
>>> next(first)
'hehe'
>>> next(first)
'hehe'
>>> next(first)
'hehe'
>>> next(first)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> list(repeat(10, 10))#itertools #std
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
Создание временных файлов
В процессе написания скрипта может потребоваться создание временных файлов, которые будут удалены автоматически после завершения работы скрипта или обработки файла.
Это может быть полезно по разным причинам - при обработке больших данных (которые не вместятся в буфер) или при проведении сложных операций (например, можно создать временный файл и натравить на него ffmpeg).
Для решения этих проблем в Python есть модуль
В процессе написания скрипта может потребоваться создание временных файлов, которые будут удалены автоматически после завершения работы скрипта или обработки файла.
Это может быть полезно по разным причинам - при обработке больших данных (которые не вместятся в буфер) или при проведении сложных операций (например, можно создать временный файл и натравить на него ffmpeg).
Для решения этих проблем в Python есть модуль
tempfile
. Нас интересует 2 функции - это TemporaryFile
и NamedTemporaryFile
.TemporaryFile
позволяет создать безымянный временный файл. Вот так можно создать временный текстовой файл, открыть его на запись и чтение (за это отвечает первый аргумент "w+t"
, подробнее можно прочитать здесь):from tempfile import TemporaryFile
with TemporaryFile("w+t") as t:
t.write("Hello, boxwithpython!")
t.seek(0)
data = t.read()
NamedTemporaryFile
используется для более продвинутых сценариев, так как он создает файл с именем, поэтому мы можем получить путь к нему и использовать его для дальнейших целей:from tempfile import
NamedTemporaryFile
with NamedTemporaryFile("w+t") as t:#std
t.write("Hello, boxwithpython!")
print(t.name) # /tmp/tmpljhsktjt
Shielded execution в asyncio
Допустим, есть следующий обработчик, который производит оплату:
Поможет нам в этом
Допустим, есть следующий обработчик, который производит оплату:
async def handler(request):Если соединение отвалится то обработчик упадет с ошибкой, так как серверу будет некуда отправлять ответ. Задача должна отмениться, но что если мы хотим, чтобы она выполнилась наверняка?
await service.pay(request.user)
return web.Response(text="payed")
Поможет нам в этом
asycio.shield()
. Он защищает задачу от отмены, даже в случае возникновения ошибки. Выглядит это следующим образом:async def handler(request):#asyncio #std
await asyncio.shield(service.pay(request.user))
return web.Response(text="payed")
Про __slots__
Python, аналогично другим динамическим языкам, таким как JavaScript, предоставляет возможность манипулирования объектами в рантайме, в том числе позволяет добавлять, изменять и удалять атрибуты. Цена этого – понижение скорости доступа к атрибутам и дополнительные расходы памяти.
Такое поведение нужно не всегда. Бывают случаи, когда мы точно знаем, какие атрибуты будут у наших экземпляров классов. Или же мы хотим ограничить добавление новых атрибутов. Именно для этого и существует
Слоты задаются через атрибут
В свою очередь, память экономится из-за того, что у класса не создается
#std #slots
Python, аналогично другим динамическим языкам, таким как JavaScript, предоставляет возможность манипулирования объектами в рантайме, в том числе позволяет добавлять, изменять и удалять атрибуты. Цена этого – понижение скорости доступа к атрибутам и дополнительные расходы памяти.
Такое поведение нужно не всегда. Бывают случаи, когда мы точно знаем, какие атрибуты будут у наших экземпляров классов. Или же мы хотим ограничить добавление новых атрибутов. Именно для этого и существует
__slots__
.Слоты задаются через атрибут
__slots__
в классе:class SlotsClass:Теперь мы не можем добавлять новые атрибуты к нашим объектам. Скорость доступа к атрибутам повышается на 25-30%, потому что при доступе к ним их больше не надо вычислять.
slots = ('foo', 'bar')
>>> obj = SlotsClass()
>>> obj.foo = 5
>>> obj.foo
# 5
>>> obj.another_attribute = 'test'
Traceback (most recent call last):
File "python", line 5, in <module>
AttributeError: 'SlotsClass' object has no attribute 'another_attribute'
В свою очередь, память экономится из-за того, что у класса не создается
__dict__
, который как раз хранил атрибуты.#std #slots
Коробка с питоном
Про __slots__ Python, аналогично другим динамическим языкам, таким как JavaScript, предоставляет возможность манипулирования объектами в рантайме, в том числе позволяет добавлять, изменять и удалять атрибуты. Цена этого – понижение скорости доступа к атрибутам…
__slots__ и наследование
Важно помнить, что при попытке унаследовать класс с
Из-за этого возникает неоднозначность, какой именно слот использовать в результирующем классе.
#std #slots
Важно помнить, что при попытке унаследовать класс с
__slots__
подкласс их унаследует, но так же и создаст __dict__
для новых атрибутов:class SlotsClass:Это стандартное и понятное поведение. Чтобы избежать создания
__slots__ = ('foo', 'bar')
class ChildSlotsClass(SlotsClass):
pass
>>> obj = ChildSlotsClass()
>>> obj.__slots__
# ('foo', 'bar')
>>> obj.foo = 5
>>> obj.test = 3
>>> obj.__dict__
# {'test': 3}
__dict__
, можно снова переопределить __slots__
в подклассе:class SlotsClass:А что с множественным наследованием?
__slots__ = ('foo', 'bar')
class ChildSlotsClass(SlotsClass):
__slots__ = ('baz',)
>>> obj = ChildSlotsClass()
>>> obj.foo = 5
>>> obj.baz = 6
>>> obj.something_new = 3
AttributeError: 'ChildSlotsClass' object has no attribute 'something_new'
class ClassA:Оно не работает. Потому-что каждый класс может иметь свои собственные
__slots__ = ('foo', 'bar',)
class ClassB:
__slots__ = ('baz',)
class C(ClassA, ClassB):
pass
TypeError: multiple bases have instance lay-out conflict
__slots__
, которые могут пересекаться с другими классами, а это может привести к тому, что объекты могут быть созданы неправильно или будут иметь непредсказуемое поведение. Из-за этого возникает неоднозначность, какой именно слот использовать в результирующем классе.
#std #slots
Решил расширить канал ещё одной тематикой - занимательными задачками.
Пока что буду писать про те, которые встречались на тех. собеседованиях.Они не всегда будут адекватные, но что уж есть :)
А начнём, как полагается с классики. Надо объяснить следующее поведение:
Вопрос в том, что здесь творится с ссылками. Разберём самую первую часть. Пробуем получить id объектов:
На вопрос, почему у них одинаковые идентификаторы ответит деталь реализации PyLong_FromLong(для искушенных читать можно читать отсюда ) , которая указывает, что интерпретатор хранит массив целочисленных объектов для всех чисел в диапазоне от -5 до 256. Поэтому, когда мы создаем переменную с числом в этом диапазоне он просто отдает ссылку на уже существующий объект.
Микрооптимизация, при чём очень важная - так уж получилось что числа из этого диапазона используются чаще всего.
В Java есть похожая оптимизация, там такой диапазон составляет от -128 до 127, но есть нюансы .
Второй вопрос отпадает сам собой (будут разные ссылки), но что будет если мы создадим файл с следующим содержимым и запустим его:
А вот это уже нюанс работы нашего REPL.
Каждая написанная нами строка в нём разбирается отдельно. Но при запуске через файл Python имеет возможность применить дополнительные оптимизации для констант, так как он видит сразу весь код - в этом и различие.
А какие ещё неочевидные моменты вы знаете с REPL или int'ами? Пишите в комменты, обсудим :)
#std #задачки
Пока что буду писать про те, которые встречались на тех. собеседованиях.
А начнём, как полагается с классики. Надо объяснить следующее поведение:
>>> a = 256
>>> b = 256
>>> a is b
True # ???
>>> a = 257
>>> b = 257
>>> a is b
False # ???
Вопрос в том, что здесь творится с ссылками. Разберём самую первую часть. Пробуем получить id объектов:
>>> a = 256
>>> b = 256
>>> id(a), id(b)
(2214170730704, 2214170730704)
На вопрос, почему у них одинаковые идентификаторы ответит деталь реализации PyLong_FromLong
Микрооптимизация, при чём очень важная - так уж получилось что числа из этого диапазона используются чаще всего.
Второй вопрос отпадает сам собой (будут разные ссылки), но что будет если мы создадим файл с следующим содержимым и запустим его:
a = 257
b = 257
print(a is b) # True
А вот это уже нюанс работы нашего REPL.
Каждая написанная нами строка в нём разбирается отдельно. Но при запуске через файл Python имеет возможность применить дополнительные оптимизации для констант, так как он видит сразу весь код - в этом и различие.
А какие ещё неочевидные моменты вы знаете с REPL или int'ами? Пишите в комменты, обсудим :)
#std #задачки
Python documentation
Integer Objects
All integers are implemented as “long” integer objects of arbitrary size. On error, most PyLong_As* APIs return(return type)-1 which cannot be distinguished from a number. Use PyErr_Occurred() to d...
Сегодня у нас простенькая задачка, а то пятница, все отдыхать хотят, я понимаю.
Есть следующий код:
Вопрос - что вернется при вызове(ну не просто так же мы собрались, верно?) , но почему?
Ответ, как обычно, есть в документации. Возвращаемое функцией значение определяется последним выполненным return.
Вторым важным аспектом является то, что finally исполняется всегда, поэтому мы и получаем его return.
#std #задачки
Есть следующий код:
def test():
try:
return 1
finally:
return 2
Вопрос - что вернется при вызове
test()
? Все и так на этом моменте понимают, что вернётся 2 Ответ, как обычно, есть в документации. Возвращаемое функцией значение определяется последним выполненным return.
Вторым важным аспектом является то, что finally исполняется всегда, поэтому мы и получаем его return.
raise
, кстати, тоже работать не будет:def test():
try:
raise ValueError()
finally:
return 3
test() # 3
#std #задачки
Python documentation
8. Errors and Exceptions
Until now error messages haven’t been more than mentioned, but if you have tried out the examples you have probably seen some. There are (at least) two distinguishable kinds of errors: syntax error...
Там в Python 3.12 добавили нашумевший PEP 659, а у меня пет-проект один давно не обновлялся, и так уж звёзды сошлись, что я сижу второй день обновляю его на 3.12
Задача - есть функционал, который под капотом имеет некоторый класс следующего вида:
Мы определяем новые классы наследуясь от
Ну прямо дженерик напрашивается! Тем более в 3.12 их завезли, красивые:
Встаёт вопрос, а как нам получить наш тип из дженерика?
Для начала, получим
Ещё можно это сделать с помощью
Теперь надо получить получить сам тип в дженерике. В этом нам поможет
Теперь в методе
Проверяем:
Вы восхитительны!
Кстати, эта же штука должна работать ещё вроде как аж с 3.8, так как в нём именно был добавлен
Ага,
#рецепт #std
Задача - есть функционал, который под капотом имеет некоторый класс следующего вида:
class BaseFunction:
serialize_to: None
def serialize(self, data: dict) -> serialize_to:
pass # тут мы используем наш serialize_to
@dataclass
class ModelA:
x: str
class FunctionA(BaseFunction):
serialize_to: ModelA
Мы определяем новые классы наследуясь от
BaseFunction
, переопределяем в них serialize_to
и вызываем serialize
который делает нам инстанс serialize_to
. Ну прямо дженерик напрашивается! Тем более в 3.12 их завезли, красивые:
class BaseFunction[T]:
def serialize(self, data: dict) -> T:
pass # тут мы используем наш serialize_to
class FunctionA(BaseFunction[ModelA]):
pass
Встаёт вопрос, а как нам получить наш тип из дженерика?
Для начала, получим
__orig_bases__[0]
- он вернёт нам классы, от которых мы наследовались. Так как нам нужен только наш первый класс, мы указываем [0]
:>>> FunctionA.__orig_bases__
__main__.BaseFunction[__main__.ModelA]
Ещё можно это сделать с помощью
get_original_bases
из types
, но его добавили только в 3.12 (почему я об этом сказал - узнаете ниже).Теперь надо получить получить сам тип в дженерике. В этом нам поможет
typing.get_args
, который получает все аргументы типа. Дополнительно укажем, что нам нужен первый тип:>>> get_args(FunctionA.__orig_bases__[0])[0]
<class '__main__.ModelA'>
Теперь в методе
serialize
класса BaseFunction[T]
можно написать штуку, которая автоматически сериализует наши данные:def serialize(self, data: dict) -> T:
type_from_generic = get_args(self.__class__.__orig_bases__[0])[0]
return type_from_generic(**data)
Проверяем:
>>> f = FunctionA()
>>> f.serialize(data={"x": 1})
ModelA(x=1)
Вы восхитительны!
Кстати, эта же штука должна работать ещё вроде как аж с 3.8, так как в нём именно был добавлен
__orig_bases__
(PEP 560), ну и под капотом у новых дженериков используется...>>> FunctionA.__mro__
(<class '__main__.FunctionA'>, <class '__main__.BaseFunction'>, <class 'typing.Generic'>, <class 'object'>)
Ага,
typing.Generic
:)#рецепт #std