Как работает bool()?
Python может принимать любой объект в булевом контексте. Чтобы определить, является ли выражение истинным или ложным, применяется функция
По умолчанию, объект попытается вызвать метод
Резюмируя, получается такая цепочка:
Python может принимать любой объект в булевом контексте. Чтобы определить, является ли выражение истинным или ложным, применяется функция
bool(x)
, которая должна вернуть булево значение (True
или False
).По умолчанию, объект попытается вызвать метод
__bool__
, который должен вернуть булево значение. Если реализованного метода нет, то Python попытается вызвать __len__
, который должен вернуть 0 или значение больше ноля - при ноле результатом будет False
, соответственно при значении больше ноля - True
. Если ни один метод не реализован, то автоматически вернется True
.Резюмируя, получается такая цепочка:
__bool__
→ __len__
→ True
#stdНемного фактов про str() и repr()
-
-
- Если метод
А что вы можете сказать про
#std
-
repr()
вызывает метод __repr__
объекта, а метод str()
, собственно __str__
.-
repr()
вызывается интерактивной оболочкой, при попытке вывести объект.- Если метод
__str__
в объекте не определен, то вызывается __repr__
. А что вы можете сказать про
str()
и repr()
? Пишите ниже, в комментарии 👇#std
Коробка с питоном
Немного фактов про str() и repr() - repr() вызывает метод __repr__ объекта, а метод str(), собственно __str__. - repr() вызывается интерактивной оболочкой, при попытке вывести объект. - Если метод __str__ в объекте не определен, то вызывается __repr__. …
Когда использовать __str__ или __repr__?
Оба магических метода используются для получения строкового представления объекта. Но как их лучше использовать - можно увидеть на картинке.
Таким образом имеем, что
#std
Оба магических метода используются для получения строкового представления объекта. Но как их лучше использовать - можно увидеть на картинке.
Таким образом имеем, что
__repr__
должен использоваться для предоставлении информации об объекте разработчику, а __str__
должен быть читаемым и использоваться для представления информации пользователю.#std
В канале недавно уже был пост про namedtuple, но мне ещё есть что про него рассказать.
Первое, что я хотел бы вам показать - именованный кортеж можно создать с помощью любой структуры данных, которая поддерживает итерирование:
Именованные кортежи неизменяемые, но мы можем использовать метод
Первое, что я хотел бы вам показать - именованный кортеж можно создать с помощью любой структуры данных, которая поддерживает итерирование:
>>> lst = ['Mining', 'Excavating', 'Boiling']Ещё мы можем создать кортеж через метод
>>> Skills = namedtuple('Skills', lst)
>>> skills = Skills(1, 2, 3)
>>> skills
Skills(Mining=1, Excavating=2, Boiling=3)
_make()
:>>> skills = Skills._make([1, 2, 3])В примере используется список, но никто не мешает вам сделать такое же с кортежем, множеством и даже словарем (но вот только значения которые вы установите для ключа игнорируются).
Skills(Mining=1, Excavating=2, Boiling=3)
Именованные кортежи неизменяемые, но мы можем использовать метод
_replace()
, чтобы изменить данные. Под капотом он использует метод _make
для создания нового кортежа, поэтому, по сути, мы просто пересоздадим кортеж:>>> skills._replace(Mining=100)#std
Skills(Mining=100, Excavating=2, Boiling=3)
Типизация именованных кортежей.
Один из комментаторов указал, что
Именованный кортеж можно создать 2 путями - через наследование
Все методы, которые были в
#std
Один из комментаторов указал, что
namedtuple()
объявлен устаревшим и в 3.10 его поддержка прекратится. Что же делать? Использовать NamedTuple
, если в вашем проекте питон выше версии 3.6.Именованный кортеж можно создать 2 путями - через наследование
NamedTuple
(класс Skills
) или старым способом (AnotherSkills
). Оба способа содержат аннотации типов, повышающие (как минимум) читаемость кода.Все методы, которые были в
namedtuple
(_make()
, _replace()
, _asdict()
) работают:>>> s = Skills(0)
>>> s._asdict()
{'mining': 0, 'excavating': 1}
Теперь мы можем посмотреть на аннотации:s.__annotations__Так же, ничего не мешает добавлять новые методы или оверрайтить существующие. В примере у
{'mining': <class 'int'>, 'excavating': <class 'int'>}
Skills
переопределен метод для str()
:>>>str(s)
'Какой качок, уровень копания: 0\nуровень лесорубства: 100'
GitHub#std
Про применение all()
Стандартная библиотека Python полна полезных вещей, но полезные вещи есть даже среди встроенных функций. Одна из таких встроенных функций - это
Функция
К примеру,
Стандартная библиотека Python полна полезных вещей, но полезные вещи есть даже среди встроенных функций. Одна из таких встроенных функций - это
all()
.Функция
all()
возвращает True
, если все элементы истинные (или объект пустой). К примеру,
all()
очень удобно использовать с генераторами. Например, мы можем проверить, являются ли все октеты в IP-адресе числами:>>> all(i.isdigit() for i in '127.0.0.b'.split('.'))
False
>>> all(i.isdigit() for i in '127.0.0.1'.split('.'))
True
#stdПоиск N максимальных или минимальных элементов.
Если вы хотите создать список из N максимальных или минимальных элементов, модуль
У этого модуля есть две функции:
Если вы хотите создать список из N максимальных или минимальных элементов, модуль
heapq
вам поможет в этом.У этого модуля есть две функции:
nlargest()
и nsmallest()
, которые, соответственно ищут максимальные и минимальные элементы. Например:>>> nums = [10, 20, 30, 40, 55, 632, -3, 98321, 82, 0, 8]
>>> heapq.nlargest(4, nums)
[98321, 632, 82, 55]
>>> heapq.nsmallest(4, nums)
[-3, 0, 8, 10]
Обе функции принимают параметр key, который позволяет их использовать с сложными структурами данных. Например:currency = [
dict(name="Etherium", price=3323),
dict(name="Bitcoin", price=45538),
dict(name="ZCash", price=132),
dict(name="Litecoin", price=184),
dict(name="OmiseGo", price=8.933)
]
>>> heapq.nsmallest(2, currency, key=lambda s: s['price'])
[{'name': 'OmiseGo', 'price': 8.933}, {'name': 'ZCash', 'price': 132}]
>>> heapq.nlargest(2, currency, key=lambda s: s['price'])
[{'name': 'Bitcoin', 'price': 45538}, {'name': 'Etherium', 'price': 3323}]
#stdОпределяем наиболее часто встречающиеся элементы в последовательности.
Допустим, у нас есть некоторая последовательность из слов (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