Как стать автором
Обновить

Комментарии 28

Спасибо огромное за статью, много интересного узнал, хоть и давно пишу на Python.

Вопрос по примеру из использования генераторов.
А если у нас большой файл, но нам надо сделать не
file.read().split("\n")

а, например,
file.read().split("<...>")


то есть другой, отличный от \n разделитель?
В данный момент использую решение 10-летней давности с offset и seek(), которое работает но не столь элегентно… Может есть решение?

Написал такое:


def custom_splitter(file, delimiter, remove_delimiters):
    counter = 0
    segment = ''
    del_length = len(delimiter)
    while char := file.read(1):
        segment += char
        counter += 1
        if counter % del_length == 0 and segment.endswith(delimiter):
            yield segment[:-del_length] if remove_delimiters else segment
            segment = ''
            counter = 0

with open('text.txt', encoding='utf-8') as file:
    for segment in custom_splitter(file, '<...>', True):
        print('-'*20)
        print(segment)

Хотя возможно, что с точки зрения производительности лучше было бы хранить и обновлять строку из последних символов, количество которых равно длине разделителя, и определять конец сегмента по ней, вместо str.endswith.

Ну так и есть :)
Правда я читал блоками по N байт, каждый блок сплитил по разделителю, а остаток (который возможно неполный) переносил в следующую итерацию…

Тоже вариант, который, вероятно, лучше моего.
Написал тот, о котором упоминал:


def custom_splitter(file, delimiter, remove_delimiters):
    end = ''
    segment = []
    while char := file.read(1):
        segment += [char]
        end += char
        if len(end) >= len(delimiter):
            if end == delimiter:
                yield ''.join(segment[:-len(delimiter)]) \
                    if remove_delimiters else ''.join(segment)
                segment.clear()
            end = end[1:]
    yield ''.join(segment)
Так-то, конечно, полезная для новичков подборка.
Но всё-таки это ни разу не методы рефакторинга, тут просто синтаксический сахар и несколько полезностей из стандартной библиотеки.
1. Включение (прим. пер.: абстракции) списков, словарей и множеств
Обычно на русский язык эти сomprehensions переводят как генераторы. То есть генератор списка, генератор словаря и генератор множества.
>>> print(f'1 USD = {usd_to_eur:.2f} EUR')
Вот то что идет после двоеточия — это задание особенностей форматирования. Их несколько существует. Вот тут собрана таблица с примерами: mkaz.blog/code/python-string-format-cookbook

Тоже видел подобный перевод. Но, по-моему, он вносит некоторую путаницу – в Python этот термин уже используется для другой штуки, о которой, кстати, говорится в 10 пункте этой статьи. Т. е., было бы так:


  1. Генераторы
    ...
  2. Использование генераторов.
1. Генераторы
Генераторы коллекций.
говорится в 10 пункте этой статьи.
ИМХО, то что в десятом пункте вообще не корректно называть генератором, я бы назвал это итератором (хотя это вопрос к автору а не переводчику).
Генератор генерирует новую коллекцию, а итератор идет по существующей. В десятом пункте идет построчный обход существующих в файле строк, то есть итерация.

Похоже, вы правы. В десятом пункте действительно выполняется итерация по итератору строк файла через вызов метода __next__, если я корректно выразился.
Наткнулся на такую, вроде бы неплохую, статью https://opensource.com/article/18/3/loop-better-deeper-look-iteration-python на эту тему.

Да, генераторы коллекций – путаницы действительно меньше.
С другой стороны, теоретически может быть путаница с вещами наподобие этой:


def what_am_i():
    lst = []
    for i in range(100):
        lst.append(i)
        yield lst[:]
Это функция-генератор.
А есть еще выражения-генераторы (generator expressions). Основное их отличие от генераторов коллекций в том, что они выдают элемент по-одному, не загружая в память сразу всю коллекцию.
Я такую классификацию в своей статье использовал:
image

Да, вроде неплохая статья. Как-то читал в документации про форматирование – тяжело все запомнить.

Смотря, что вам нужно.
Обычный .format() с пустыми фигурными покроет большинство потребностей и будет весьма прозрачным.

Лично я в основном избегаю генераторов списков. Они дают сравительно небольшое увеличение производительнсти, зато здорово снижают читабельость кода.
Когда накапливается опыт их применения и чтения, то они наоборот в большинстве не слишком навороченных случаев увеличивают читабельность кода из-за компактности и однозначности записи.
squared_evens = [n ** 2 for n in numbers if n % 2 == 0]
Или
squared_evens = []
for n in numbers:
    if n % 2 == 0:
        squared_evens.append(n ** 2)

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


squared_evens = [n**2
                 for n in range(10)
                 if n % 2 == 0]
Ну я бы сказал, в простейших случаях ничего, но чуть посложнее и нужно разбираться, что же там происходит.
Ваш пример ничего, но получается практически тот же for.
С другой стороны, я люблю вставлять для отладки печать или запись в лог, опять-таки это делать удобнее в for.
Вообще, я стараюсь писать так, чтобы в одной строке был один «логический кирпичик».

Он визуально стал похож на for, но вряд ли потерял преимущество в производительности, которое вы упомянули. Насчет отладки согласен.

Конечно, если использовать тернарные выражения и более одного цикла / вложенные включения – тогда действительно прощай читабельность.

Еще можно путаницы добавить, если фильтрацию и ветвление одновременно использовать:
list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
list_b = [x**3 if x < 0 else x**2 for x in list_a if x % 2 == 0]
# вначале фильтр пропускает в выражение только четные значения
# после этого ветвление в выражении для отрицательных возводит в куб, а для остальных в квадрат
print(list_b)   # [-8, 0, 4, 16]

Как раз подобное имел в виду.

тривиальный случай, поняла с первого раза. там всё же английским по белому написано

  1. Множественное присваивание и распаковка кортежей
    >> # Вместо этого:
    >> code = 404
    >> message = "Not Found"
    >> 
    >> # Характерный для Python способ:
    >> code, message = 404, "Not Found"

Отличный пример, но читаемость ни разу не выросла. На code review я за такое бью по рукам.


  1. Тернарное выражение
    >> # Вместо этого: 
    >> if got_even_number():
    ...     state = "Even"
    ... else:
    ...     state = "Odd"
    ... 
    >> # Характерный для Python способ:
    >> state = "Even" if got_even_number() else "Odd"

Тернарный оператор — очень скользкая дорожка. Чаще я сталкиваюсь с таким:


state = "Long description in case of `got_even_number` returns 1" \
    if got_even_number() else \
    "Another long description used when `got_even_number` returns 0"

В данном примере обычный if предпочтительнее. А ещё лучше обойтись вообще без if:


states = (
    "Another long description used when `got_even_number` returns 0",
    "Long description in case of `got_even_number` returns 1",
)
number = got_even_number()
state = states[number]

Пример без if — умно. Для примера в статье, вероятно, лучше подойдет словарь?

Очень субъективно, и распаковка и тернарный оператор — отличные инструменты, повышающие читаемость кода, если их применять к месту. В примерах выше я бы предпочел распаковку.

Форматирование через f-строки сложно назвать улучшением. Они бывают удобными, но имеют ряд моментов:


  1. Если забыть f перед строкой — получаем нерабочую строку, которую порою трудно отследить.
  2. Возможность вставлять различные конструкции внутрь фигурных скобок ухудшает читабельность всей строки. И это плохо, поскольку в рамках проекта желательно все строки строить единым образом, а сложить все рядышком в метод format уже не получится.

Конечно, все это отчасти вкусовщина, но я не вижу потенциальной выгоды f-строк перед format'ом.

Отчасти, отследить строку с забытой перед ней f помогает подсветка фигурных скобок, по крайней мере в Visual Studio Code с расширением Python такое наблюдаю. Конечно, это не так заметно, по сравнению с сообщениями линтера.
В одной статье читал, что str.format якобы плох в том случае, когда в нем используются именованные аргументы, а именно в этой: https://realpython.com/python-f-strings/

очевидные вещи — такие очевидные

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации