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)
Ответ на: комментарий от anonymous

Весь фронт стилем K&R написан. А весь бэк стилем Олмана. :)

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

Как это записать без 4 уровней вложенности, если такую архитектуру наговнокодили? И при чем тут Ruby?

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

Как это записать без 4 уровней вложенности, если такую архитектуру наговнокодили?

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

И при чем тут Ruby?

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

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

То есть self.site.connection_server.tor_manager.enabled на Питоне лучше читается, чем self.site.connection_server.tor_manager.enabled на Руби. Океееей, как скажешь, начальник.

Выкинуть говноархитектора на мороз.

Это был опенсорц. Письма можно направлять сразу в спортлото.

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

Попробуй немного подумать о том, в какой код проще вносить изменения.

более читабелен

Потому что ты с синтаксисом Руби не знаком? ну это конечно весомый аргумент.

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

Потому что ты с синтаксисом Руби не знаком? ну это конечно весомый аргумент.

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

peregrine ★★★★★
()

питон говно и ты тоже говно

/thread

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

Так у тебя про питон точно такой же аргумент.

Два года пишу на Питоне, до сих пор с синтаксисом не знаком. И другие невероятные истории от трещащего шаблона питонофанов.

Ря, не нравится, ря думать надо, ря тут руби есть который я знаю, ря дай на него всё перепишу.

Хорошо тебя триггернуло.

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

Для справки. Я пишу на:

  • Python
  • Ruby
  • PHP
  • JavaScript
  • CoffeeScript
  • sh
  • C
  • C++

В общем, какая есть необходимость по заказу, на том и пишу.

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

Сколько лет пишешь? У меня почему-то ощущение что 2 года ты и пишешь, а начинал с ублюдочного C++, который оставил неизгладимую травму на всю жизнь.

Блин, ты 8 лет уже в теме. Явно кресты виноваты.

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

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

wandrien ★★
() автор топика

Господа, деликатно прерву вашу полемику на секундочку. Вот как бы это можно было сделать на D:


auto getSupportedTrackers()
{
	bool tor  = site.connection_server.tor_manager.enabled;
	bool ipv6 = "ipv6" !in site.connection_server.supported_ip_types;

	getTrackers.map!(t=>
		(tor || ".onion" !in t) &&
		getAddressParts(t) &&
		(ipv6 || helper.getIpType(getAddressParts(t)["ip"] != "ipv6")
	);
}

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

В общем, не ругайтесь. Всем добра.

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

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

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

Вот это я просто опечатался, там конечно должен быть `return`:

    return getTrackers.map!(t=>
	(tor || ".onion" !in t) &&
	getAddressParts(t) &&
	(ipv6 || helper.getIpType(getAddressParts(t)["ip"] != "ipv6"));

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

адрес функции

байто-пердольский бред

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

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

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