LINUX.ORG.RU

list comprehensions

 , ,


1

1

В очередной раз при правке кода на питоне у меня пригорело от list comprehensions.

А вот что пишут настоящие живые люди, которых никто не заставляет под дулом пистолета:

I find the list comprehension much clearer than filter+lambda

Или:

Personally I find list comprehensions easier to read. It is more explicit what is happening from the expression [i for i in list if i.attribute == value] as all the behaviour is on the surface not inside the filter function.

Ну давайте посмотрим, как этот much clearer way выглядит in the wild. Как-то так:

    def getSupportedTrackers(self):
        trackers = self.getTrackers()

        if not self.site.connection_server.tor_manager.enabled:
            trackers = [tracker for tracker in trackers if ".onion" not in tracker]

        trackers = [tracker for tracker in trackers if self.getAddressParts(tracker)]  # Remove trackers with unknown address

        if "ipv6" not in self.site.connection_server.supported_ip_types:
            trackers = [tracker for tracker in trackers if helper.getIpType(self.getAddressParts(tracker)["ip"]) != "ipv6"]

        return trackers

Просто сплошной [blabla for blabla in blablas if ...blabla...].

Просто в начале каждой такой строки ты должен мысленно стирать кусок [tracker for tracker in trackers if и читать, что же там дальше. И как писал Роберт Мартин в «Чистом коде», любые конструкции, которые принуждают читателя тренироваться пропускать себя мимо глаз, являются источником скрытых ошибок. Пропустив 500 раз мимо глаз типовой фрагмент кода, на 501-й раз вы пропускаете ПОЧТИ такой же фрагмент, в котором содержится ошибка. И в силу одинаковой натренированности рефлексов у всех разработчиков продукта, эта ошибка может оставаться незамеченной годами.

Давайте посмотрим, как этот же код можно преписать на лямбдах на руби:

    def getSupportedTrackers():
        trackers = @getTrackers()

        if not @site.connection_server.tor_manager.enabled
            trackers = trackers.filter {|tracker| not tracker.include? ".onion"}
        end

        trackers = trackers.filter {|tracker| @getAddressParts(tracker)}

        if not @site.connection_server.supported_ip_types.include? "ipv6"
            trackers = trackers.filter {|tracker| helper.getIpType(@getAddressParts(tracker)["ip"]) != "ipv6"}
        end

        return trackers
    end

Уже стало лучше за счёт уменьшения количества бойлерплейта, который приходится пропускать мимо. Но 3 вызова trackers.filter подряд и два идентичных вызова getAddressParts говорят нам, что этот код надо переписать.

Заметьте, что необходимость рефакторинга для устранения дублирования не была очевидна в коде с list comprehensions, потому что они за своей многословностью и нечитабельным синтаксисом скрывают суть происходящего.

Убираем дублирование:

    def getSupportedTrackers()
        tor_enabled = @site.connection_server.tor_manager.enabled
        ipv6_enabled = @site.connection_server.supported_ip_types.include? "ipv6"

        trackers = @getTrackers()

        trackers = trackers.filter {|tracker|
            if (not tor_enabled) and (tracker.include? ".onion")
                next false
            end

            address_parts = @getAddressParts(tracker)
            if not address_parts
                next false
            end

            if (not ipv6_enabled) and (helper.getIpType(address_parts["ip"]) == "ipv6")
                next false
            end

            next true
        }

        return trackers
    end

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

P.S. Или для любителей длинных однострочников:

    def getSupportedTrackers()
        tor_enabled = @site.connection_server.tor_manager.enabled
        ipv6_enabled = @site.connection_server.supported_ip_types.include? "ipv6"

        trackers = @getTrackers()

        trackers = trackers.filter {|tracker|
            next false if (not tor_enabled) and (tracker.include? ".onion")

            address_parts = @getAddressParts(tracker)
            next false if not address_parts

            next false if (not ipv6_enabled) and (helper.getIpType(address_parts["ip"]) == "ipv6")

            next true
        }

        return trackers
    end

Но этот вариант по моему мнению хуже.

Перемещено leave из talks

★★

Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от ei-grad

Ну в первую очередь он нужен вместо map, чтоб не писать некрасивую lambda.

Тут перепутаны причина и следствие. lambda некрасивая, потому что по мнению авторов Питона она должна быть некрасивой. А желательно — вообще не быть. Поэтому вместо map() с лямбдой используется уникальный синтаксический костыль.

Тут в качестве анекдота можно вспомнить такой язык как CoffeeScript, в котором автор собрал всё, что только нашел. Единственное, для чего там нужны comprehensions — это чтобы в документации на язык писать такие примеры «на английском»:

foods = ['broccoli', 'spinach', 'chocolate']
eat food for food in foods when food isnt 'chocolate'

Что по задумке автора должно транслироваться в такой код JS:

foods = ['broccoli', 'spinach', 'chocolate'];

for (l = 0, len2 = foods.length; l < len2; l++) {
  food = foods[l];
  if (food !== 'chocolate') {
    eat(food);
  }
}

Как по мне, транспилированный код выглядит более осмысленно.

Почему я пишу «единственное, для чего там нужны» — потому что нормальные лямбды в языке тоже есть:

[-10, -5, 0, 1, 2, 3].filter (x) -> x > 0
wandrien ★★
() автор топика

Подумаешь, нашёл один кейс где filter лучше подходит. Да, есть такие кейсы. Есть кейсы где фильтр выглядит ещё хуже чем LC здесь.

PS. Вообще, я бы под условиями собрал композицию фильтров и применил её один раз. Как-то так:

    def getSupportedTrackers(self):
        filt = lambda _: True

        if not self.site.connection_server.tor_manager.enabled:
            filt = lambda tracker: filt(tracker) and ".onion" not in tracker

        filt = lambda tracker: filt(tracker) and self.getAddressParts(tracker)

        if "ipv6" not in self.site.connection_server.supported_ip_types:
            filt = lambda tracker: filt(tracker) and helper.getIpType(self.getAddressParts(tracker)["ip"]) != "ipv6"

        return list(filter(filt, self.getTrackers()))

или

    def getSupportedTrackers(self):
        filters = []

        if not self.site.connection_server.tor_manager.enabled:
            filters.append(lambda tracker: ".onion" not in tracker)

        filters.append(lambda tracker: self.getAddressParts(tracker))

        if "ipv6" not in self.site.connection_server.supported_ip_types:
            filters.append(lambda tracker: helper.getIpType(self.getAddressParts(tracker)["ip"]) != "ipv6")

        return [tracker for tracker in self.getTrackers() if all(filter(tracker) for filter in filters)] 

Для первого случая я бы поискал ченить готовое для красивой функциональной композиции функций.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Для первого случая я бы поискал ченить готовое для красивой функциональной композиции функций.

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

И получить в итоге то же самое условие в 3-х слоях сахара.

wandrien ★★
() автор топика
Ответ на: комментарий от wandrien

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

anonymous
()
Ответ на: комментарий от wandrien

нужно пойти искать «ченить» для заворачивания лямбды в лямбду лямбды.

Это просто функциональный подход, ничего страшного в нём нет. Три ифа полностью эквивалентны композиции трёх функций.

Вся суть современного программирования

Этот код в два раза короче твоего «компактного» говна на ruby и отлично читаем.

Вместо того, чтобы написать простейшее условие

Хочешь простейшее условие - вариант с генератором тебе предложили. Вообще, все твои варианты переписываются на питоне 1 в 1.

slovazap ★★★★★
()
Ответ на: комментарий от wandrien

Тут в качестве анекдота можно вспомнить такой язык как CoffeeScript, в котором автор собрал всё, что только нашел. Единственное, для чего там нужны comprehensions — это чтобы в документации на язык писать такие примеры «на английском»:

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

anonymous
()
Ответ на: комментарий от slovazap

Три ифа полностью эквивалентны композиции трёх функций.

…в которых находятся те же самые if-ы под словом and.

То есть твои «функции» не нужны.

Это просто функциональный подход, ничего страшного в нём нет.

А нечего переводить стрелки на функциональный подход, оправдывая свой говнокод. Я сейчас могу завернуть три if-а в объекты и гордо сказать, что это ООП. Но правда такой хернёй я заниматься не буду, мне даже лень писать код ради такого нелепого примера.

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

Этот код в два раза короче твоего «компактного» говна на ruby и отлично читаем.

А если записать в одну строчку, будет еще компактнее. Дерзай.

А уж читаемость за грань всего.

Вообще, все твои варианты переписываются на питоне 1 в 1.

Вообще тема про антипаттерн применения comprehensions была и не задумывалась как питоносрач, но любо-дорого посмотреть, как припекает у фанбоев. По теме сразу видно, кто использует язык, чтобы писать код и решать задачи, а кто – чтобы фанбойствовать.

В общем, ваш баттхерт был незапланированным эффектом. Сорян.

wandrien ★★
() автор топика
Ответ на: комментарий от slovazap

Да ты же гений декомпозиции. Вместо одной анонимной функции раскидал по коду целую пачку, и добавил коллекцию на ровном месте. Единственная причина так всё усложнять, это неполноценность питоновской лямбды. И сразу же видно простор для рефакторинга: отдельные методы для каждого фильтра, потом вынос их в отдельный класс. Короче, пошла плясать губерния. И в реальном проекте тебя скорее всего так и заставят сделать, ибо «красивая функциональная композиция» это чота сложно.

anonymous
()
Ответ на: комментарий от wandrien

Вообще тема про антипаттерн применения comprehensions

и не задумывалась как питоносрач

Ну да, ну да.

vvn_black ★★★★★
()
Ответ на: комментарий от wandrien

Я сейчас могу завернуть три if-а в объекты и гордо сказать, что это ООП.

Воот, ты верно уловил куда ветер дует.

anonymous
()
Ответ на: комментарий от wandrien

…в которых находятся те же самые if-ы под словом and.

То есть твои «функции» не нужны.

Ещё раз, эти вещи эквивалентны. Можно ифами, можно функциями. Ограничивать себя чем-то одним можно только по некомпетентности. Что лучше в данном конкретном случае зависит только от случая. Здесь - одинаково.

В общем, ваш баттхерт был незапланированным эффектом. Сорян.

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

А если записать в одну строчку, будет еще компактнее. Дерзай.

А уж читаемость за грань всего.

Так что просвещайся.

slovazap ★★★★★
()
Ответ на: комментарий от bread

А в чём проблема «выноса лямбд в функции»? Просто присваиваешь идентификатор, с помощью которого разжёвываешь, что происходит в коде, и заодно представляешь для переиспользования, в рамках которого до кучи может вскрыться необходимость переиспользования уже написанной кем-то ранее логики из библиотек.

Princesska ★★★★
()
Ответ на: комментарий от slovazap

Ещё раз, эти вещи эквивалентны. Можно ифами, можно функциями.

А можно еще функции в объекты завернуть. А объекты в темплейты… Ой, в питоне нет темплейтов. Ну ничего: переписать на C++ и таки завернуть.

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

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

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

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

Когда текст связный, его просто читать и легко править.

Держи список литературы, тебе будет полезно:

  1. Мартин Фаулер, «Рефакторинг»
  2. Роберт Мартин, «Чистый код»
  3. Макконнелл, «Совершенный код»
wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)

Самая большая проблема твоей «критики» заключается в том, что ты жалуешься на вкус кактуса. Все варианты обсуждаемой конструкции — это сорта сахара, ко вкусу можно привыкнуть. Да, некоторые вкусы объективно хуже, некоторые — лучше, но в остальном это ложка сахара в бочке говна. Я вот лично в своем обзоре проблем питона даже не удосужился упомянуть «проблему» list comprehension.

byko3y ★★★★
()
Ответ на: комментарий от grem

Всё гораздо проще оказалось:
пригорело от list comprehensions

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

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

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Я вот лично в своем обзоре проблем питона даже не удосужился упомянуть «проблему» list comprehension.

Про костыли, интегрированные в язык так, что хочешь не хочешь, а используешь их – другой разговор. Такой разговор имеет смысл до начала проекта при выборе языка.

Что касается сабжа, то эту «проблему» можно свободно не использовать.

Самая большая проблема твоей «критики» заключается в том, что ты жалуешься на вкус кактуса.

Самая большая проблема некоторых комментаторов в том, что они решили, что я почему-то критикую язык. Я критикую конкретный код.

Все варианты обсуждаемой конструкции — это сорта сахара, ко вкусу можно привыкнуть.

Всё, что не машинный код, является тем или иным видом сахара. Но ты не пишешь в машинных кодах. Почему же?

wandrien ★★
() автор топика
Ответ на: комментарий от Princesska

А в чём проблема «выноса лямбд в функции»?

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

bread
()
Ответ на: комментарий от bread

А причём тут Python? list comprehension есть много где.

Тут уже приводили CoffeeScript, вот, например, в F# тоже такое есть:

// программа
let list = [ for a in 1 .. 10 -> (a * a) ]
printfn "%A" list
// вывод
[1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
fsb4000 ★★★★★
()
Ответ на: комментарий от bread

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

Ну по-правде сказать, для типичного кода на питоне проблема «лямбда vs именованная функция» не доставляет больших хлопот.

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

А вот типичный код на JS вида «стрелка стрелка стрелка стрелка» на питон нормально не перепишешь. Но в JS он как раз уместен.

wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от fsb4000

вот, например, в F# тоже такое есть:

Здесь сабж уместно выглядит.

wandrien ★★
() автор топика
Ответ на: комментарий от wandrien

Сейчас как раз рефакторю фрагмент с выносом лямбд в именованные методы класса

Да, обычно так и делают. Хотя в большинстве случаев это не нужно, потому что никакого переиспользования не будет. А просто понадобилось if вставить, и идешь рефакторить.

bread
()
Ответ на: комментарий от fsb4000

list comprehension есть много где

Да уж, много. Я не против, пусть будет. Но такие трюки в языке, который не относится к expression-oriented, выглядят дико. Как и тупая лямбда в одно выражение.

bread
()
Ответ на: комментарий от wandrien

Про костыли, интегрированные в язык так, что хочешь не хочешь, а используешь их – другой разговор. Такой разговор имеет смысл до начала проекта при выборе языка

Неа, в числе проблем были генераторы, динамические классы, множественное наследование.

Самая большая проблема некоторых комментаторов в том, что они решили, что я почему-то критикую язык. Я критикую конкретный код

На питоне ты лучше не напишешь. Ты сам приводишь пример руби, как более годного варианта. И кто из нас теперь не критикует язык?

byko3y ★★★★
()
Ответ на: комментарий от byko3y

На питоне ты лучше не напишешь. Ты сам приводишь пример руби, как более годного варианта. И кто из нас теперь не критикует язык?

Если в языке есть comprehensions и есть нормальные лямбды, то нужно просто использовать лямбды. Питоном языки не ограничиваются.

На питоне ты лучше не напишешь.

Даже обычный for по элементам коллекции будет лучше, чем стартовый пример поста.

Вот из другого файла, например. Старый добрый for никто не запрещал:

        protocols = set()
        for tracker_address in supported_trackers:
            protocol = self.getNormalizedTrackerProtocol(tracker_address)
            if not protocol:
                continue
            if protocol == "udp" and not self.isUdpEnabled():
                continue
            protocols.add(protocol)

        protocols = list(protocols)

Или засунуть весь фарш в методы и делать так:

    def getWorkingTrackers(self):
        return self.getTrackers(self.isTrackerWorking)
wandrien ★★
() автор топика
trackers = [tracker for tracker in trackers if ".onion" not in tracker]


Кокой-то жабастайл. Потому и выглядит как жаба.

urxvt ★★★★★
()
Ответ на: комментарий от byko3y

Ты сам приводишь пример руби, как более годного варианта. И кто из нас теперь не критикует язык?

Ну давай руби покритикую для равновесия.

Возьмём код на питоне и попробуем переписать на руби так, чтобы стало «лучше», то есть на самом деле, стало хуже:

        protocols = set()
        for tracker_address in supported_trackers:
            protocol = self.getNormalizedTrackerProtocol(tracker_address)
            if not protocol:
                continue
            if protocol == "udp" and not self.isUdpEnabled():
                continue
            protocols.add(protocol)

        protocols = list(protocols)
        protocols = supported_trackers.map {|tracker_address|
            @getNormalizedTrackerProtocol(tracker_address)
        }.filter {|protocol|
            protocol
        }.filter {|protocol|
            @isUdpEnabled() or protocol != "udp"
        }.uniq

Надо как-то назвать этот метод написания кода. Скажем, «бесконтрольное серийное размножение лямбд».

Наверняка уже есть для него названия, но я не знаю.

wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от byko3y

Ну есть всякие map, правда в python3 он теперь возвращает итератор вместо list, поэтому иногда приходится делать, например, [*map()], если на выходе нужен list. Есть ещё пара способов.

grem ★★★★★
()
Ответ на: комментарий от wandrien

Если в языке есть comprehensions и есть нормальные лямбды, то нужно просто использовать лямбды. Питоном языки не ограничиваются

Блин, так в питоне и нет нормальных лямбд. Я потому и пишу «на питоне ты лучше не напишешь».

Старый добрый for никто не запрещал

Такое себе. List comprehension объективно лаконичнее при сравнимой читаемости.

byko3y ★★★★
()
Ответ на: комментарий от wandrien

Ащет было бы актуальнее писать:

protocols = supported_trackers.map {|tracker_address|
    @getNormalizedTrackerProtocol(tracker_address)
}.filter {|protocol|
    protocol && (@isUdpEnabled() or protocol != "udp")
}.uniq

Что вполне себе лаконично.

byko3y ★★★★
()
Ответ на: комментарий от bread

Какого рефакторинга? У тебя по сути используется анонимная функция, которую ты просто делаешь именованной. Дальше уже по именам и их использованию видишь, всё ли с кодом в порядке.

Princesska ★★★★
()
Ответ на: комментарий от byko3y

Ащет было бы актуальнее писать:

См. первый коммент темы с 4-мя лямбдами.

Что вполне себе лаконично.

В таком виде это нормально.

Скажем так, в ходе переработки кода такие цепочки часто приходится расписывать на отдельные присваивания переменных, иначе цепочки стремятся разрастись на весь экран. И потом вся эта простыня с map{}.filter{}.uniq{}.map{}.sort{}.map.with_index доходит до точки, где нужно весь этот код удалить и переписать.

wandrien ★★
() автор топика
Ответ на: комментарий от wandrien

Пардон, я тут вообще не в теме, мимо проходил.

По-моему это всё - какое-то издевательство над компутером.

Сравнения строк, проверка флага функцией. Не понятное использование list, фишка которого в разнотипных элементах списка.

Почти наверняка ваял бы что-то про битовые маски. И на протоколы по биту, и на enabled их в конце отдельная маска один раз. На кой шут тут вообще массивы/списки?

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

Извините, случайно вырвалось )

Toxo2 ★★★★
()
Ответ на: комментарий от bread

Я подозреваю, что костыли типа сабжевых comprehensions как раз и добавлены, чтобы таким не страдать.

Приведите пожалуйста пример, где в лямбду нельзя добавить «пару строк» а в comprehensions можно. А то пока стойкое ощущение что то ли Вы вообще не понимаете о чем говорите, либо просто не в состоянии нормально сформулировать свою мысль;-(

AntonI ★★★★★
()
Ответ на: комментарий от wandrien

лямбды в питухоне очень сильно ограничены, они всегда однострочные, а сделать многострочными их нельзя потому программа программируется отступами, ага. если корячить лямбды, то код становится ещё больше write-only.

а по поводу ехал ехал через ехал - это по-моему влияние перла, там тоже есть такие еврейские конструкции которые читаются справа-налево, а не слева-направо. влияние write-only языка даёт write-only код на питухоне.

anonymous
()
Ответ на: комментарий от bread

В comprehensions легко добавить if, а в лямбду нет, капитан.

lambda x: x if x%2 else x*2

не благодари.

Особенно эпично это Ваше высказывание выглядит в контексте данной темы, где if уже добавлен а аналогом является filter где if искарбоки.

Спорить с адептами «питухона», это такое себе. Мне надоело.

Так Вы бы сразу сказали что Вам 12 лет и Вы являетесь адептом АУЕ - никто бы с Вами тут и не начинал спорить… Всего хорошего.

AntonI ★★★★★
()
Ответ на: комментарий от wandrien

только на кложе эта лямдбда вышла бы короче и читабельнее, что-то вроде

(defn supported? [tracker]
  (and (or tor-enabled (str/includes? tracker ".onion"))
       (when-let [{:keys [ip]} (get-address-parts tracker)]
         (or ipv6-enabled (= "ipv6" (helper/get-ip-type ip))))))
anonymous
()
Ответ на: комментарий от anonymous

только на кложе эта лямдбда вышла бы короче и читабельнее, что-то вроде

Да)))) Но скобочки — это беда))

byko3y ★★★★
()
13 марта 2021 г.

Вкусовщина

Ну давайте посмотрим, как этот much clearer way выглядит in the wild. Как-то так: [...]

Имхо, так себе код. Написан на скорую руку. И даже в таком виде он прост и понятен. Согласны же?

Просто сплошной [blabla for blabla in blablas if ...blabla...].

Да. Это и называется list comprehension. Одна сущность, которая заменяет filter, map, lambda, find, select, detect и фиг знает что ещё.

Именно за это её любят — за простоту и универсальность. Вам не нравится? Ну, дело вкуса.

Этот код хотя и выглядит не так компактно при взгляде на экран издалека, на самом деле проще в чтении и в поддержке.

Заменили 8 строк на 18 и они стали проще в чтении и поддержке? Странный у вас вкус.

А можно полтора раза короче, без фильтров и лямбд:

def getSupportedTrackers(self):
    result = []
    for tracker in self.getTrackers():
        if not self.site.connection_server.tor_manager.enabled and ".onion" in tracker:
            continue
        parts = self.getAddressParts(tracker)
        if not parts:
            continue
        if "ipv6" not in self.site.connection_server.supported_ip_types and helper.getIpType(parts["ip"]) == "ipv6":
            continue
        result.append(tracker)
    return result
И тоже без дублирований.

А можно вынести все условия в отдельную функцию:

def isSupportedTracker(self, tracker):
    tor_on = self.site.connection_server.tor_manager.enabled
    ipv6_on = "ipv6" in self.site.connection_server.supported_ip_types
    parts = self.getAddressParts(tracker)
    return (tor_on or ".onion" not in tracker) and parts and (ipv6_on or helper.getIpType(parts["ip"]) != "ipv6")

def getSupportedTrackers(self):
    return [tracker for tracker in self.getTrackers() if self.isSupportedTracker(tracker)]

Без дублирований, коротко и просто.

И

каждая строка выражает свою мысль без лишних бессмысленных слов, которые нужно отфильтровывать глазами.

Хотя, может, вам и не понравится. Что ж, дело вкуса...

anonymous
()
Ответ на: Вкусовщина от anonymous

Вы действительно считаете, что мусор tracker for tracker in trackers if, который несколько раз повторяется в теле функции — подходит под название «прост и понятен»? Окей. Но я не буду в очередной раз повторять все аргументы из обсуждения, почему этот код плох, нечитабелен и приводит к ошибкам. Всё, что можно было на эту тему сказать, уже сказано.

Здесь нарушена идея о несмешении абстракций разного уровня, но если вы этого не видите… ну значит, не видите. Что я могу с этим сделать? Наверное, ничего.

Одна сущность, которая заменяет filter, map, lambda, find, select, detect и фиг знает что ещё.

Но в программировнии задача не в том, чтобы соревноваться, кто изобретёт самую мощную убервафлю, заменяющую собой все остальные вафли, а в том, чтобы составить корректный лексикон для описания проблемы и её решения на формальном языке. Здесь эта задача не решена.

А можно полтора раза короче, без фильтров и лямбд:

Я уже писал где-то выше: лучше кода из стартового примера может быть практически всё, что угодно. И старый добрый for in в том числе.

А можно вынести все условия в отдельную функцию:

И так тоже можно.

Заменили 8 строк на 18 и они стали проще в чтении и поддержке? Странный у вас вкус.

Вы архитектуру приложения в строках кода измеряете?

Я вот как раз сейчас рефакторю портянку, в которой один и тот же код дублируется 6 раз. Ну как, код… модуль строк на 3000.

Это произошло потому, что кто-то экономил строчки, чтобы «8 строк не превратились в 18» и писал модуль одним сплошным полотном.

А потом, когда потребовалось сделать еще один модуль, который делает «почти то же самое, но чуть иначе», рефакторить было уже некогда… потому что мы на проде, и у нас горит задница, ну или я не знаю, почему. Как так вышло. Но понятно, что что-то пошло не так, и архитектура приложения оказалась не готова к такого рода вызовам. Как результат — 6 модулей дублей, внутри которых на всех уровнях функций внесены правки. И поддерживать дальше это уже невозможно без переписывания.

Это только фронт. А есть еще бэк… Не будем о грустном.

wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 4)
Ответ на: комментарий от wandrien

Тут в треде путаются два разных вопроса. Даже три.

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

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

В-третьих, что List comprehension плохо влияет на читаемость кода — нет, это не так. Наоборот, из-за «плоской» структуры, на мой взгляд, он читается легче, чем 3-4 вложенных вызова.

У вас иначе? Сравните, например, один и тот же код на javascript и python:

a = new Array(a.length-offset).fill().map( (c,i) => a[i] + a[i+offset] );

a = [ a[i] + a[i+offset] for i in range(len(a)-offset) ]

Какой из них вам понятнее? Как бы этот код записали вы?

Теперь представьте, что код выглядит так:

a = [ a[i] + a[i+offset] for i in range(len(a)-offset) if a[i+offset] < i ]

Как бы вы его записали теперь?

Вы действительно считаете, что мусор tracker for tracker in trackers if, который несколько раз повторяется в теле функции — подходит под название «прост и понятен»?

Если вы его быстро поняли, то да, он прост и понятен. Хорош ли он? Не особо. Мне он не нравится. Но сложнее он от этого не становится.

А что именно вас «цепляет» в этой конструкции? Слово tracker, которое повторяется два раза, но кажется, что три?

Ну, например, конструкция t for t in self.getTrackers() вам бы меньше бросалась в глаза?

Но в программировнии задача не в том, чтобы соревноваться, кто изобретёт самую мощную убервафлю, заменяющую собой все остальные вафли, а в том, чтобы составить корректный лексикон для описания проблемы и её решения на формальном языке. Здесь эта задача не решена.

Не совсем. Язык нужен не для того, чтобы «составить корректный лексикон», а чтобы решать задачи было легче.

Чем легче язык решает данную задачу, тем он лучше.

Вот и list comprehension хорош тогда, когда упрощает решение задач.

А потом, когда потребовалось сделать еще один модуль, который делает «почти то же самое, но чуть иначе», рефакторить было уже некогда… потому что мы на проде, и у нас горит задница

И такое бывает. Но это бывает на любом языке. Питон тут ничем не хуже и не лучше.

anonymous
()
Ответ на: комментарий от anonymous

А что именно вас «цепляет» в этой конструкции?

-------------------------+
                         |   +-----------------------+
                         v   v                       |
a = [ a[i] + a[i+offset] for i in range(len(a)-offset) if a[i+offset] < i ]
      ^                  |   |    ^                     ^               |
      |                  +--------+                     |               |
      |                      |                          |               |
      |                      +--------------------------+               |
      +-----------------------------------------------------------------+

<sarcasm> Оч удобно читать. Ничего не напрягает, всё последовательно и логично. </sarcasm>

А есть еще:

a = [i if i > 0 else 0 for i in blabla]

{then-val} if {cond} else {else-val}!!! Я на этой строчке поста задумался: а может питон так называется потому, что устроен про принципу свернувшейся в клубок змеи, и читать его надо по спирали? Или это всё неудачная штука Monty Python?

А еще бывают list comprehensions для { ... }.

А еще они бывают последовательными и вложенными, что есть две разные вещи, которые не нужно путать.

А с появлением the walrus operator в 3.8 писать write-only код в list comprehensions стало проще!

Python стремится к более простому, менее громоздкому синтаксису и грамматике

[...]

Python придерживается философии «должен существовать один — и, желательно, только один — очевидный способ сделать это»

[...]

Алекс Мартелли, член Python Software Foundation, и автор книг по Python пишет, что «Описывать что-то как „умное“ не считается комплиментом в культуре Python» (англ. To describe something as 'clever' is not considered a compliment in the Python culture).

[...]

* Красивое лучше, чем уродливое.
* Явное лучше, чем неявное.
* Простое лучше, чем сложное.
* Сложное лучше, чем запутанное.

Написано одно,на деле полностью другое. Куча ad-hoc костылей ка каждый случай вместо универсального подхода.

Тут где-то в другой теме кто-то хвастался функцией maketrans(). Вот тут я конечно прифигел, что кому-то такое приходит в голову.

Какой из них вам понятнее?

С лябмдой, конечно. Но что так, что так, в данном случае write only. Такой код проще переписать на простой цикл, читабельность от этого только выиграет.

wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

Спасибо за псевдографическую диаграмму! Кажется, я понимаю, что вас смущает. Но... вам это только кажется неудобным. 🙂

Но что так, что так, в данном случае write only. Такой код проще переписать на простой цикл, читабельность от этого только выиграет.

Вспомните конструкцию:

for(int i = 0; i < a.size()-offset; ++i)
Вы же не считаете, что она write-only, и её надо переписать в:
int i = 0;
while(i < a.size()-offset)
{
  ...
  ++i
}

Если for() вас не смущает, а list comprehension смущает, то, думаю, это просто ваша привычка, самообман.

Сейчас я вам перечислю аргументы, а вы скажете, начиная с какого из них вы не согласны.

(1) Инфиксная конструкция проще префиксной : +(*(*(a,b),c),/(/(a,b),c)) против a*b*c+a/b/c

(2) И вообще «плоская» конструкция проще вложенной : ((((a*b)*c))+(((a/(b))/(c)))) против a*b*c+a/b/c

(3) Исходя из (1) и (2) вложенная префиксная конструкция if(x<y)(f(x,y))(f(y,x)) сложнее и тяжелее для чтения, чем плоская инфиксная f(x,y) if x<y else f(y,x)

Реальная причина, почему многим такая конструкция не нравится — это банальная привычка. «Я привык, что в моём любимом языке сначала идёт условие, почему тут иначе?»

Но есть ли на самом деле эта привычка? Сравните две строки:

return f(x,y) if x<y else f(y,x)
return if (x<y) (f(x,y)) (f(y,x))
И попробуйте их «прочитать» на русском. Первый вариант намного ближе к естественному «Вернём эф от икс и игрек если икс меньше игрек, иначе вернём эф от игрек и икс». Вы же не говорите «вернём если икс меньше игрек эф от икс игрек ...»?

То есть вы уже привыкли именно к такому инфиксному порядку, даже не замечая этого. 🙂

(4) «Объектная» запись проще «функциональной» потому что она уменьшает визуальную вложенность: tor_manager(connection_server(site(self))) против self.site().connection_server().tor_manager()

(5) Также как и в (4) «плоская инфиксная» запись проще «вложенной префиксной»: a.assign(a.map(function(i){return i*i})) против a = [i*i for i in a]

С какими из этих пунктов вы не согласны?

А еще они бывают последовательными и вложенными, что есть две разные вещи, которые не нужно путать.

Вызовы функций тоже бывают последовательными и вложенными, и это тоже две разные вещи. Вы же их не путаете?

Простое лучше, чем сложное.

Написано одно,на деле полностью другое. Куча ad-hoc костылей ка каждый случай вместо универсального подхода.

Конкретно в нашем случае — всё именно так, как написано. «Простое лучше, чем сложное. Плоское лучше, чем вложенное.» Потому инфиксное лучше, чем префиксное. Поэтому инфиксный if в тернарках. И инфиксный for в list comprehension.

Единый стиль, разве нет?

К тому же list comprehension универсальнее, чем map/filter/lambda. Не верите? Тогда попробуйте переписать через map/filter/lambda тот пример:

a = [ a[i] + a[i+offset] for i in range(len(a)-offset) if a[i+offset] < i ]
Получится?

anonymous
()
Ответ на: комментарий от wandrien

А с появлением the walrus operator в 3.8 писать write-only код в list comprehensions стало проще!

С этим не спорю. Поэтому я им почти никогда не пользуюсь.

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

Пример:

if match1:=re.match("pattern1", data):
    result = match1.group(1)
elif match2:=re.match("pattern2", data):
    result = match2.group(2)
elif match3:=re.match("pattern3", data):
    result = match3.group(1) + match3.group(2)
else:
    result = None
Сможете переписать этот код на любом другом языке без «walrus operator», чтобы стало проще?

Если не сможете, то «walrus operator» выполнил свою задачу — сделал код более понятным.

Куча ad-hoc костылей ка каждый случай вместо универсального подхода.

«Readability counts.» Все эти «ad-hoc костыли» уменьшают вложенность кода и потому улучшают его читаемость.

anonymous
()
Ответ на: комментарий от anonymous

Все примеры «в пользу» питона на самом деле имеют прямой control flow без сюрпризов. Это либо линейное исполнение для цепочки методов, либо произвольное (не влияющее на результат) исполнение для математических операций. И т.п.

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

А так и в Ruby можно писать:

return f(x,y) if x<y
return f(y,x)

И это безобразие к сожалению даже попало в руководства по стилю и линтеры.

Всё это как раз делается ради «красоты» и «привычности чтения непрограммистом», а реальной пользы для написания ПО — ноль, либо отрицательная. Ну нету в формальном тексте интонации, чтобы показать инверсию или наличие эллипсиса.

На примере из двух коротких строк это всё смотрится красиво, вот только в реальном коде заставляет каждую строку читать с конца, чтобы удостовериться, что в ней нет ветвления.

Это полностью ломает автоматику/моторику чтения императивного кода, когда у нас слева отступами и ключевыми словами в начале строк показана суть дела, то есть control flow, а далее за ними идут детали в виде выражений.

Даже в случае когда для управления исполнением применяются || и &&, обычно в пределах одной строки есть либо И, либо ИЛИ, но не оба. Потому что разбирать получившееся не очень-то приятно даже с расставленными скобками. Здесь идея о чёткой записи ветвлений тоже не нарушатся. Хотя в случае нетривиальных ветвлений, выполненных на булевых операциях, стоит тоже давать по рукам.

инфиксное лучше, чем префиксное

Так дойдём до маразма и будем весь код в инфиксной записи писать. %)

for(int i = 0; i < a.size()-offset; ++i)

Это тоже, мягко говоря, нетривиальная конструкция. Студентов специально приходится учить её читать и понимать. Но даже здесь логика чтения практически не нарушается. Сначала инициализация. Затем проверка условия. Третья часть в самом конце. Тело цикло логически расположено перед третьей частью, а физически после. Это да, есть такое.

wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 4)
Ответ на: комментарий от anonymous

elif match2:=re.match("pattern2", data):

Здесь всё хорошо как раз. Стоило бы даже в семейство Си-подобных внести запрет на использование = в контексте условий и добавить туда отдельную операцию.

А насчёт control flow дополню вот чем.

У меня есть компилятор самописного игрушечного язычка, написанный на нём самом. И вот там я применил такую штуку как опциональное указание на разновидность оператора в конце.

То есть можно написать просто:

if a < b then
    ...
end

А можно:

if a < b then
    ...
end:if

И так для всех блочных операторов: end:while, end:for, end:switch, end:function.

Так вот. Буду эмоционален, но: ЭТО НЕВЕРОЯТНО УЛУЧШАЕТ ЧИТАЕМОСТЬ КОДА И СНИЖАЕТ ЦЕНУ ТРИВИАЛЬНЫХ ОШИБОК.

Вчера у меня в JS работа трижды вставала на 2-3 минуты из-за того, что я где-то оставлял несбалансированную скобку и тупо сидел и искал, где. А уж особенно если там больше 10 уровней вложенности, как в коде, который я рефакторил вчера. Это еще та задачка — всё растащить по методам, ничего не сломав.

Кстати идея добавить такой синтаксис меня посетила как раз в ходе рефакторинга кода компилятора.

wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

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

А должна ли? В смысле, действительно ли человеку так проще?

Арифметические операции выполняются после вычисления операндов. Но при этом люди обычно пишут не a b * c * a b / c / +, а всё-таки a * b * c + a / b / c.

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

Не с конца, просто целиком. Но строку всегда приходится читать целиком, чтобы её выполнить.

Иначе никак. Тернарный оператор объективно нужен. Без него люди начинают писать x<y and f(x,y) or f(y,x). А это не только хуже читается, но ещё и приводит к ошибкам, если f(x,y) вернёт ноль.

Так дойдём до маразма и будем весь код в инфиксной записи писать. %)

Оно уже так и есть. 🙂 Например, все предпочитают a = ... вместо a.assign(...), или тем более assign(a, ...).

Между строками бывают префиксные операторы — строка может начинаться с if, for, while, return... А дальше внутри строки почти всё инфиксное. Либо инфиксное, либо скобки, добавляющие уровень вложенности.

anonymous
()
Ответ на: комментарий от wandrien

Вчера у меня в JS работа трижды вставала на 2-3 минуты из-за того, что я где-то оставлял несбалансированную скобку и тупо сидел и искал, где.

Обычно из-за этого и используют стиль Олмана вместо стиля K&R — так несовпадающие скобки лучше заметны.

Но в целом это — извечная проблема того, что человек читает код по отступам, а компьютер — по скобкам. Когда они не совпадают — возникает проблема.

У неё есть 3 подхода к решению.

  • Сделать «скобки» разными для разных конструкций. Тогда, если скобка забыта — будет понятно, у кого её забыли. Тут и bash (if/then/fi, while/do/done...), и php (endwhile, endif), и даже basic (next i, next j).
  • Синхронизировать скобки и отступы с помощью автоформатирования: indent, astyle, clang-format...
  • Сделать язык, который компьютер тоже читает по отступам, а не по скобкам: python, nim, coffeescript...

Что забавно. Именно это считается чуть ли не главным «недостатком» питона. 🙂

anonymous
()

Всё же какой ублюдочный синтаксис у этого руби.

ipv6_enabled = @site.connection_server.supported_ip_types.include? "ipv6"
Тьфу.

peregrine ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.