Size: a a a

PyWay – гуру Python 🐉

2021 April 26
PyWay – гуру Python 🐉
🤖 Любителям отлаживать принтами (к коим и я порой отношусь) очень понравится.

На заре мы писали так, когда хотели распечатать значение переменной рядом с ее именем:

my_var, foo = 10, "abc"
print('Debug my_var =', my_var, 'and foo = "' + foo + '"')

Потом нам показали f-строки, и мы стали писать так:

print(f'Debug my_var = {my_var} and foo = "{foo}"')

И все бы ничего, если бы не приходилось каждый раз дублировать название переменной до знака "равно" и после в фигурных скобках!
К счастью в версии Python 3.8 нас ждал королевский подарок, а именно возможность печатать за один раз и название, и значение переменной, просто добавив в конце знак "равно". Вот так:

print(f'Debug {my_var=} and {foo=}')
# Напечатает: Debug my_var=10 and foo='abc'

Если кавычки для строки не нужны, можно указать спецификатор !s:

print(f'{foo=!s}')  # foo=abc

Удивительно, но пробелы, которые вы оставите в строке формата, также перенесутся на результат:

print(f'{foo = }')  # foo = 'abc'

Причем это может быть необязательно переменная, а целое выражение, причем левая часть будет ровно в той форме, что и в форматной строке, ну не прекрасно ли это?

print(f'{len([1, 2, 3]) = } and {10 + 5 * 3 = }')
# Напечатает: len([1, 2, 3]) = 3 and 10 + 5 * 3 = 25

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

print(f'{my_var = :.2f}')
# my_var = 10.00

Или вот такое оформление:

print(f'{foo = :-^20}')
# foo = --------abc---------


Теперь ваша отладка принтами (или логгирование) станет легче и быстрее.
источник
2021 May 18
PyWay – гуру Python 🐉
✅ Хотим обратить ваше внимание на полезный telegram-канал для обучения высокоуровневому языку программирования Python

На канале ежедневно публикуются задачи по Python и Machine Learning: алгоритмы, функции, классы, регулярные выражения, итераторы, генераторы, ООП, исключения, numpy, pandas, matplotlib, scikit-learn, TensorFlow и многое другое!

✔️Станьте специалистом по Python вместе с каналом "Задачи по Python и машинному обучению"
источник
2021 May 24
PyWay – гуру Python 🐉
Давайте уже закончим серию постов про dataclass.
Сегодня рассмотрим возможность наследования dataclass.
Сходу рассмотрим пример:

from dataclasses import dataclass

@dataclass
class Parent:
   x: int

@dataclass
class Child(Parent):
   y: str

c = Child(20, "String")

Как видите, наследование достаточно прямолинейно. Однако, стоит отменить, что в конструкторе Child сначала идут родительские атрибуты (x из Parent), и только потом атрибуты (y из Child).

Декоратор @dataclass можно указать только у класса-наследника, если вы не планируете использовать родительские классы в качестве dataclass. Декоратор пробегается по всей цепочке классов (MRO) в обратном порядке (от object к Child), то есть сначала Parent, потом Child, и собирает все подходящие поля (обозначенные аннотацией типа или как field) в упорядоченный словарь. Сгенерированные методы будут опираться на набор полей именно из этого словаря.

Вообще, не запрещено в дочерних классах повторять поля из родительских, даже меняя их тип и значение по умолчанию. Например, допустимо в Child добавить x: str = "344". Тогда x приобретет аннотацию строки в классе Child. Однако, я бы не рекомендовал этого делать без особой надобности, так как это может ввести в заблуждение при чтении кода.

Частая проблема, которая встречается при наследовании dataclass: если в Parent есть поля со значением по умолчанию, то и во всех его наследниках dataclass ВСЕ поля должны быть со значениями по умолчанию. Дело как раз в порядке следования аргументов в инициализаторе. Родительские поля идут сначала, и за ними просто не может быть обязательных полей, синтаксис такого не позволяет:

def __init__(self, x = 5, y, z):  # нельзя

Возможное решение проблемы заключается в том, чтобы создать вспомогательные классы, разделив поля на: а) просто поля без умолчания и б) поля с ним. А потом в Child и Parent наследоваться от нескольких классов сразу, собрав нужные поля и их однофамильцев со значениями по умолчанию:

@dataclass
class _ParentBase:
   name: str
   age: int

@dataclass
class _ParentDefaultsBase:
   smart: bool = False

@dataclass
class _ChildBase(_ParentBase):
   school: str

@dataclass
class _ChildDefaultsBase(_ParentDefaultsBase):
   smart: bool = True

@dataclass
class Parent(_ParentDefaultsBase, _ParentBase):
   pass

@dataclass
class Child(Parent, _ChildDefaultsBase, _ChildBase):
   pass

from inspect import signature

print(signature(Parent))
# (name: str, age: int, smart: bool = False) -> None
print(signature(Child))
# (name: str, age: int, school: str, smart: bool = False) -> None


Как раз таки это именно тот случай, когда повторное определение полей с теми же именами имеет смысл.
Кстати, функция signature позволяет посмотреть сигнатуру метода и установить, все ли поля и их дефолтные значения на месте.

В итоге решение получилось немного громоздко. Стоит ли игра свеч, решать вам.
источник
2021 May 26
PyWay – гуру Python 🐉
Друзья, хочу прорекламировать вам автора, который делает крутой контент на канале @remshai

🌐 В нем вы найдете материалы по программированию, разработке сайтов и прохождению технических собеседований.

🐍 Отличительная черта данного блога  — молодой автор, который совсем недавно прошел путь от Intern до Middle разработчика и готов поделиться своим опытом

Подписывайтесь на канал
👇🏻
https://t.me/remshai
источник
2021 May 29
PyWay – гуру Python 🐉
Пост-инициализация dataclass

Так как метод __init__ в dataclass генерируется декоратором автоматически, а иметь второй такой метод в классе нельзя, то в случае, если разработчик хочет совершить дополнительные действия при инициализации класса, есть решение. После основной инициализации будет вызван магический метод пост-инициализации __post_init__ ("пост" значит "после"). В нем можно вычислить значения одних полей на основе других. Например, создать описание продукта из его названия и цены:

from dataclasses import dataclass, field, InitVar

@dataclass
class Product:
   name: str
   price: float
   desc: str = field(init=False)  # будем вычислять

   def __post_init__(self):
       self.desc = f"{self.name}: ${self.price:.2f}"

print(Product('Масло', 3.5))
# Product(name='Масло', price=3.5, desc='Масло: $3.50')


⚠️ Если в настройках класса вы выключили init=False, то __post_init__ не будет вызываться.

Вообще __post_init__ может принимать параметры. Это параметры только для инициализации – особые поля dataclass, помеченные аннотацией InitVar. Если таковые имеются, то они будут переданы как параметры в __post_init__ из конструктора при создании экземпляра класса. Больше они никак не используются и не будут храниться в экземпляре.
По сути они нужны для настройки процесса пост-инициализации. Допустим в прошлом примере мы введем флаг, который будет отключать генерацию описания (desc):

@dataclass
class Product:
   name: str
   price: float
   desc: str = field(init=False, default='')
   generate_desc: InitVar[bool] = True  # по умолчанию описание генерируется

   def __post_init__(self, generate_desc):
       if generate_desc:
           self.desc = f"{self.name}: ${self.price:.2f}"

print(Product('Масло', 3.5))
# Product(name='Масло', price=3.5, desc='Масло: $3.50')

print(Product('Масло', 3.5, generate_desc=False))
# Product(name='Масло', price=3.5, desc='')


Параметр generate_desc можно передать при создании экземпляра класса, но потом в классе его уже нет, он используется только в __post_init__.

P.S. Все вырученные средства от рекламы в этом канале идут на благотворительность, а именно в приют 🐈 "Планета кошек" в Нижнем Новгороде.
источник
2021 June 07
PyWay – гуру Python 🐉
Хей, питонисты!
@pythonnation
@pythonnation это канал для тех кто хочет изучить Python

Очевидно, что залог успеха в программировании - это регулярная практика. Именно это и есть на канале @pythonnation
@pythonnation🐍

А ещё:
— Машинное обучения простыми словами
— Задачки которые заставят вас хорошенько подумать
— Ответы на вопросы с собеседований

Особенно заценили проекты по ИСКУССТВЕННОМУ ИНТЕЛЛЕКТУ их там более 500 + КОД:
ТЫК
источник
2021 June 27
PyWay – гуру Python 🐉
Зачем нужен __new__?

Скорее всего, вы знаете и используете метод __init__ в классах для инициализации полей. Однако, при создании класса ему предшествует вызов метода __new__.

Специальный метод __new__ должен вернуть экземпляр класса (создав его при необходимости), который затем будет передан первым аргументом в соотвествующий __init__. Поскольку сам __init__ получает уже готовый экземпляр и всего лишь инициализирует его поля, ничего не возвращая, он не может повлиять на тип объекта или его источник. Конечно, непосредственным созданием экземпляра занимается сам Python в глубинах object.__new__, но кастомный __new__ обычно используется в следующих сценариях:

1. Подмена типа. Пользователь создает объект одного типа, а по факту получает объект другого типа, например, более специализированный.
2. Когда нужно не создавать новый объект, а отдать уже созданный ранее. Например, синглтон создается однажды, сохраняется и используется на протяжении всего времени работы программы.
3. Замена __init__.

ℹ️ __new__ – это всегда классовый метод, но к нему НЕ нужен декоратор classmethod.

Давайте рассмотрим тривиальный пример:

class Bulka:
   def __init__(self, a, b):
       print(f'Bulka init {a=}, {b=}')

   def __new__(cls, *args, **kwargs):
       print(f'{cls=}, {args=}, {kwargs=}')
       # cls=<class '__main__.Bulka'>, args=(5,), kwargs={'b': 7}
       return super().__new__(cls)

Bulka(5, b=7)


Из вызова Bulka(5, b=7), мы попадаем в Bulka.__new__, где cls равен Bulka, а в args и kwargs хранятся все изначально переданные аргументы. Мы просто их распечатываем на экране и возвращаемся к поведению по умолчанию, а именно вызову return super().__new__(cls). Обратите внимание, что тут мы передаем только cls, опуская все прочие параметры (версия 3.3+) Но вы должны в любом случае оставить *args, **kwargs в заголовке __new__, даже если их не используете явно внутри тела этого метода. Вместо  *args, **kwargs можно указать явный список параметров с их именами, аннотациями и дефолтными значениями, если вам так удобно.

Также, можете не пытаться изменить содержимое args и kwargs. args – это вообще кортеж, он неизменяем. А изменения в kwargs не отразятся в __init__. Однако разрешено вообще не использовать __init__, а задать все поля нового экземпляра прям в __new__.

Вот пример класса вообще без __init__, на голом __new__.

class Coffee:
   def __new__(cls, volume = '300ml', milk: bool = False):
       obj = super().__new__(cls)
       obj.volume = volume
       obj.milk = milk
       return obj

c = Coffee()
print(f'{c.volume=}, {c.milk=}')  # c.volume='300ml', c.milk=False


Фактически __init__ экономит вам всего лишь две строки. Пример от разработчиков Python – datetime.

А вот пример реализации шаблона "синглтон".

class Singleton(object):
   _instance = None  # хранит единственный экземпляр

   def __new__(cls, *args, **kwargs):
       if not cls._instance:
           # первый вызов – создаем реально объект
           cls._instance = object.__new__(cls)
       return cls._instance

print(Singleton() is Singleton())  # True


Вот еще один пример, в нем мы подменяем тип на выходе.

class Maslo:
   def __init__(self):
       print(f'Maslo')

class Bulka:
   def __init__(self, a, b):
       print(f'Bulka init {a=}, {b=}')

   def __new__(cls, *args, **kwargs):
       if not args and not kwargs:
           # пусть если без аргументов, то масло!
           return Maslo()
       else:
           return super().__new__(cls)

print(Bulka(5, b=7))  # Bulka init a=5, b=7
print(Bulka())  # Maslo


Хотите изучить остальные эталонные примеры использования __new__? Просто зайдите в папку lib в вашем установленном Python и выполните команду: grep -l "def __new__(" *.py Их найдется целая куча в файлах:

_py_abc.py, _pydecimal.py, _pyio.py, _threading_local.py, abc.py, codecs.py, datetime.py, enum.py, fractions.py, functools.py, pathlib.py, pstats.py, sre_constants.py, ssl.py, turtle.py, typing.py, weakref.py

Еще __new__ используется в метаклассах, но это отдельная история.
источник
2021 August 25
PyWay – гуру Python 🐉
🗑️ Очистка списка

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

def scruffy(arr: list):
 arr = []
 print(f'Готово! {arr=}')  # Готово! arr=[]

x = [1, 2, 3]
scruffy(x)
print(x)  # по-прежнему, [1, 2, 3]


У Скраффи не получилось сделать свою работу! Как быть? Есть два основных приема:

Первый: метод clear()

>>> x = y = [1, 2, 3]
>>> x.clear()
>>> x is y
True


Второй вариант, это использование слайсов (срезов): x[:] = []

>>> x = y = [1, 2, 3]
>>> x[:] = []
>>> x is y
True


В обоих случаях x продолжает ссылаться на тот же объект списка (мы сохранили ссылку не него в y), но из него удалены все элементы.

Вообще, срезы – очень мощная система работы со списками. Когда-то, на заре этого канала, я уже писал о них. Настало время напомнить некоторые их фишки в следующем посте.
источник
2021 September 03
PyWay – гуру Python 🐉
источник
PyWay – гуру Python 🐉
Срезы (или слайсы – от. slice) позволяют получить гибкий доступ к частям списков, кортежей, строк и других последовательных типов. Причем, как на чтение, так и на запись и удаление. Ниже я приведу несколько примеров использования срезов.

😋 Помните! Индексирование в Python идет с 0. Первый элемент имеет индекс 0.

Общая нотация среза выглядит так: arr[start:stop:step], где start, stop и step могут быть как числами, так и переменными или выражениями, но кроме того могут быть и вовсе пустым местом. start – это индекс элемента, с которого начинается выборка включительно, stop – индекс, на котором выборка останавливается (не включительно), step – это шаг, по умолчанию он равен 1. Все эти параметры могут быть и отрицательными, что придает им особый смысл.

🤭 Обращение по срезу не бросает исключений при выходе за границы исходной коллекции! Просто полученное множество может оказаться пустым.

Пусть есть исходный список:
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Все элементы, начиная с индекса 1:
arr[1:] == [2, 3, 4, 5, 6, 7, 8, 9, 10]
arr[5:] == [6, 7, 8, 9, 10]  # или с 5 индекса
arr[100:] == []  # за пределами просто пустое множество, нет ошибки

Отрицательные значения заставляют отсчитывать элементы с конца коллекции.

Четыре последних элемента:
arr[-4:] == [7, 8, 9, 10]

ℹ️ Если вам нужен конкретный элемент, считая с конца:
arr[-1] == 10  # последний
arr[-2] == 9  # предпоследний
arr[-10] == 1  # первый – самый дальний с конца
arr[-100]  # IndexError! выскочили за пределы

Вернемся к срезам. Получить первые три элемента:
arr[:3] == [1, 2, 3]

Первые элементы, не считая 6 последних:
arr[:-6] == [1, 2, 3, 4]

Диапазон. Тут индекс 4 включен (элемент равный 5), в индекс, равный 7 (это элемент со значением 8 ) уже нет.
arr[4:7] == [5, 6, 7]
arr[100:200] == []  # за пределами просто пустое множество, нет ошибки

Диапазон с 5-го элемента с конца до 2-го элемента с конца.
arr[-5:-2] == [6, 7, 8]
arr[-2:-5] == []  # порядок важен, потому что 2-ой с конца идет позже 5-го с конца!

С 8-го с конца до 4-го с начала:
arr[-8:4] == [3, 4]

С индекса 2 с начала до 3-го элемента с конца:
arr[2:-3] == [3, 4, 5, 6, 7]

Работа с шагом.

Каждый второй элемент, иными словами индексы 0, 2, 4 и так далее:
arr[::2] == [1, 3, 5, 7, 9]

Каждый третий элемент:
arr[::3] == [1, 4, 7, 10]

Каждый второй элемент, со сдвигом в 1 (индексы 1, 3, 5, ...):
arr[1::2] == [2, 4, 6, 8, 10]

Элементы c индекса 1 до 8 с шагом 4:
arr[1:8:4] == [2, 6]

Шаг не может быть нулем, иначе какой же это шаг? Это стояние на месте:
arr[::0]  # ValueError

Отрицательный шаг всегда разворачивает подмножество. Список задом наперед:
arr[::-1] == [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Каждый второй элемент с конца (индексы 9, 7, 5 и так далее):
arr[::-2] == [10, 8, 6, 4, 2]

С отрицательным шагом начальный индекс должен быть позже конечного:
arr[-2:-5:-1] == [9, 8, 7]
arr[5:2:-1] == [6, 5, 4]

Не выйдет досчитать с 2 до 5 в отрицательную сторону. После индекса 2 следует 1, таким образом, до 5 мы никогда не дойдем.
arr[2:5:-1] == []
arr[-5:-2:-1] == []


⚠️ Пропуски между двоеточиями подразумеваются как None, а не как 0:
arr[::-2] == arr[None:None:-2] == [10, 8, 6, 4, 2]
arr[0::-2] == [1]  # читай: начни с 0 индекса и шагай до упора влево по 2.

Условие start=None можно читать как "с самого края", а stop=None можно читать как "до упора".
С какого до какого края – это зависит от знака step, как вы понимаете.

Подобным образом можно работать и со строками, вырезая из них подстроки.
"hello world"[1:-3] == 'ello wo'
"hello world"[-2::-1] == 'lrow olleh'

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

В следующем посте расскажу про запись и удалением по срезам.
источник
2021 September 15
PyWay – гуру Python 🐉
✂️ Работа со срезами на запись и удаление

Во-первых, самое главное. При присвоении по срезу к другой переменной создается поверхностная копия данных!
Поверхностная (shallow) копия – это копирование ссылок на нужные элементы, при этом содержимое элементов не копируется.

arr = [1, 2, 3, 4, 5, 6, 7, 8]
bar = arr[1:6]
bar[3] = 8888
print(bar)  # [2, 3, 4, 8888, 6] - новый изменен
print(arr)  # [1, 2, 3, 4, 5, 6, 7, 8] – старый без изменений!

Лайфхак. Копирование списка: arr_copy = arr[:]

⚠️ Но! При манипуляциях над самой коллекцией – копии не создается, данные меняются в исходном объекте! Поэтому перед каждым примером подразумеваем, что arr заново равен исходному [1, 2, 3, 4, 5, 6, 7, 8].

Замена 2-го и 3-го элементов на другие:
arr[2:4] = [777, 555]
print(arr)  # [1, 2, 777, 555, 5, 6, 7, 8]

Замена может содержать любое количество элементов, как большее, так и меньшее, или равное размеру заменяемого диапазона. Размер списка может при этом измениться.

Заменим последние два элемента на 4 новые:
arr[-2:] = [11, 12, 13, 14]
# arr == [1, 2, 3, 4, 5, 6, 11, 12, 13, 14]

Вставим в середину, а именно с 4-го индекса несколько новых чисел:
arr[4:4] = [111, 222, 333]
# arr == [1, 2, 3, 4, 111, 222, 333, 5, 6, 7, 8]

Присвоить пустой список равносильно вырезанию. Вырежем первые 5 элементов:
arr[:5] = []
# arr == [6, 7, 8]

Кстати, тот же эффект достигается выражением:
del arr[:5]

Все становится немного сложнее, если использовать шаг. Тогда количество заменяемых элементов должно быть в точности равно количеству замещающих.

Заменить каждый второй элемент:
arr[::2] = [10, 20, 30, 40]
# arr = [10, 2, 20, 4, 30, 6, 40, 8]

arr[::2] = [10, 20, 30]  # ValueError (не хватает чисел в правой части)
arr[::2] = [10, 20, 30, 40, 50]  # ValueError (слишком много)

Можно также и с конца:
arr[::-2] = [10, 20, 30, 40]
# arr == [1, 40, 3, 30, 5, 20, 7, 10]

С правой стороны присваивания необязательно должен быть список. Может быть и генератор.
Например, обратим каждый третий элемент списка генератором:
arr[::3] = map(lambda x: 1 / x, arr[::3])
# arr == [1.0, 2, 3, 0.25, 5, 6, 0.14285714285714285, 8]

Удаление элементов с 1 по 7 (не включая 7), через 2:
del arr[1:7:2]
arr == [1, 3, 5, 7, 8]

Так как присвоение и удаление по срезу изменяют содержимое объекта, то такие операции не применимы к иммутабельным типам данных, таким как кортежи, строки, байты. Обычно их цели: списки и bytearray.
источник
PyWay – гуру Python 🐉
🤓 Задачка на смекалку

itertools.repeat – бесконечный генератор повторяющихся значений.

Что будет при выполнении этого кода?
источник
PyWay – гуру Python 🐉
Программа:
Анонимная викторина
39%
Возьмет сколько нужно значений из генератора
29%
Зависнет и съест всю память
17%
Выдаст ошибку о неправильной длине
16%
arr не изменится
Проголосовало: 186
источник
2021 September 23
PyWay – гуру Python 🐉
Пояснение про копирование 🤏🏻

В прошлом посте вот этот пример вызвал вопросы:

arr = [1, 2, 3, 4, 5, 6, 7, 8]
bar = arr[1:6]
bar[3] = 8888
print(bar)  # [2, 3, 4, 8888, 6] - новый изменен
print(arr)  # [1, 2, 3, 4, 5, 6, 7, 8] – старый без изменений!


Как же так? Ты говоришь про поверхностное копирование, но приводишь пример, где элементы копируются полностью? Да, я использовал числа для простоты примера, и может показаться, что копия полная, а не поверхностная. Докажу вам обратное:

list1 = [333, 444]
list2 = list1[:]  # копия
id(list1[0]) == id(list2[0])  # True


Оба списка имеют один и тот же объект (по тому же самому адресу) в своем составе!
Просто числа – объекты неизменяемые, иными словами, когда вы присваиваете число, то не меняете содержимое объекта по текущему адресу, вы привязываете переменную к новому объекту в другом месте памяти. А равные числа выглядят одинаково, независимо от того, в каком месте памяти они лежат 🙄

Для наглядности, давайте в качестве элемента списка возьмем изменяемый тип, например, опять же список. Изменения в элементе списка list1 затронут копированный поверхностно список list2.

list1 = [ [], [] ]
list2 = list1[:]
list1[0].append('surprise')
print(list2[0])  # ['surprise']


Таким образом, поверхностная копия копирует только: количество элементов и их порядок, а сами элементы остаются ссылками на старые объекты. То есть если вы начнете добавлять, удалять, менять местами элементы list1, то порядок в list2 не пострадает. Но если вы сможете изменить внутреннее содержимое элементов списка list1, которые остались в list2, тогда оно будет затронуто в обоих местах.

Для полной глубокой копии используют библиотечную функцию deepcopy, которая рекурсивно копирует объекты:

from copy import deepcopy
list1 = [ [], [] ]
list2 = deepcopy(list1)  #
👈
list1[0].append('surprise')
print(f"{list1 = }, {list2 = }")
# list1 = [['surprise'], []], list2 = [[], []] - изменение не отразилось
id(list1[0]) == id(list2[0])  # False – разные объекты!


Надеюсь, что объяснил. Если что, спрашивайте еще.
источник
2021 October 10
PyWay – гуру Python 🐉
Вот так новость! Python выходит на первое место в известном рейтинге языков программирования TIOBE! 🐍
источник
2021 November 04
PyWay – гуру Python 🐉
🎁 takewhile: бери, пока дают

В модуле itertools есть функция takewhile. Она позволяет выбирать из последовательности значения до тех пор, пока условие возвращает True.

У нее два параметра: predicate – предикат, или иными словами проверяющая функция и iterable – последовательность – это может быть список, кортеж, строка, итератор и т.п.

Код функции выглядит достаточно просто, примерно так:

def takewhile(predicate, iterable):
   for x in iterable:
       if predicate(x):
           yield x
       else:
           break

Как только predicate вернет эквивалент False на очередном элементе, то цикл остановится.
Функция возвращает не список, а генератор. Если нужен список, то оборачиваем вызов в функцию list.

Давайте по примерам. Выбрать из списка все первые элементы меньше 10:

from itertools import takewhile
numbers = [1, 2, 5, 9, 11, 42, 3]
numbers10 = list(takewhile(lambda x: x < 10, numbers))
print(numbers10)  # [1, 2, 5, 9]


Заметьте, что 3 не попала в результат, потому что встретив число 11 takewhile остановила работу. Это отличает ее от filter, которая всегда пройдет до конца.

Пример 2. Количество пробелов в начале строки:

string = "       Wat?  "
print(sum(1 for _ in takewhile(lambda x: x == ' ', string)))  # 7
источник
2021 November 10
PyWay – гуру Python 🐉
У takewhile в модуле itertools есть брат близнец из параллельной вселенной. Функция dropwhile, как можно понять из названия, будет пропускать элементы коллекции, пока выполняется условия. Ее аргументы такие же, как и у рассмотренной выше takewhile: функция проверки каждого элемента и собственно итерируемый объект.

Пока условие выполняется, функция dropwhile шагает далее. Как только впервые условие не выполнится для очередного элемента, функция начнет отдавать из себя элементы (включая этот) по очереди до конца.
dropwhile – это также генератор, и если нужен список, то обернем ее вызов в list.

Давайте посмотрим пример. Допустим дана последовательность чисел и нужно отбросить все отрицательные числа с начала:

from itertools import dropwhile

numbers = [-5, -4, -1, 0, 10, -10, 22]
numbers1 = list(dropwhile(lambda x: x < 0, numbers))
print(numbers1)  # [0, 10, -10, 22]

Число -10 осталась, потому что после встречи с первым неотрицательным числом, функция отдаст его и абсолютно все последующие элементы (в отличие от filter).

Примечательно, что для любой функции и любой коллекции на входе выполняется условие:
numbers == list(takewhile(func, numbers)) + list(dropwhile(func, numbers))  # True
(Это если, конечно, не возникло ошибок)
источник