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

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

А почему вы не упомянули Pycharm?
В комменты приглашается sobolevn
Имена
В большинстве случаев шаблонные имена достаточны. И они общеприняты. Конкретно по key и value: есть словарь с вполне понятным именем (контекстом), я перебираю в цикле его ключи и что-то делаю с его значениями. Зачем мне использовать имена с контекстом? А они к слову могут быть весьма длинными.

Называть переменные одной буквой — нормально?
Да, нормально, но только если уместно. for i in range(100):... Любой программист читающий эту часть кода поймёт, что за переменная i, более того, в таком коде большинство программистов ожидают именно эту переменную, так уж исторически сложилось. А именно для того, чтобы программист понял контекст имена переменным и дают, не правда ли?

Консистентность
Если не ошибаюсь, то сlass Some(object) и class Some совсем не одно и то же. https://wiki.python.org/moin/NewClassVsClassicClass

F-строки ужасны?
Нет, это просто ещё один инструмент. Инструмент мы выбираем по ситуации. Могу привести кучу примеров, как из кода, так и из жизни, но надеюсь и так понятно.

Числа
Спасибо КО. Литеры и константы, первые страницы любого учебника по программированию.

Используете ли вы ’@staticmethod’?
Да, я использую. И вам советую. Все мы помним мантру, что функция/метод не должны быть длинее n строк. При разбиении может получится статический метод. Почему я должен выносить его из класса? Потому что кто-то не любит ’@staticmethod’? Выносить в отдельную функцию имеет смысл, если эта функция будет использоваться где-то ещё, иначе выгодней оставить метод в составе класса.

Логика в __init__.ру — хорошо или плохо?
Полностью с вами согласен.

Функция hasattr часто вам нужна?
Не убедили. Приведите хоть пару примеров, а то голословно как-то. Почему я должен вместо нужного мне инструмента использовать другой, предназначенный для других целей? Потому что вы так считаете?
Давайте дружить?) Полностью с вами согласен, хотел только добавить, что мантра «не засовывать логику в шаблон» озвученная в разделе об F-строках ужасно надоела. Это всё продолжение той же истории, что и про @staticmethod и прочую разбивку кода на основе «синтаксиса», а не смысла. Люди с огромной радостью проглатывают идею складывать все компоненты в одну директорию, делать тонкие html-шаблоны и т.д. Потому что так не надо использовать голову. Чуть лучше, но в ту же степь, все эти проверки на количество операций в строчке, количество переменных, методов и классов. Хороший код структурируется на основе логической взаимосвязи его кусков, а не «синтаксиса». Длинное выражение, функция или класс могут в принципе не иметь разбивки на смысловые куски. Тот же вызов username.title() в шаблоне — восхитителен. При чтении мы сразу видим, зачем вызывается метод title. Мы автоматически пропускаем вызов метода, если нас не интересует содержание строчки. Этот вызов логически привязан к шаблону и должен быть с ним как можно ближе.

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

подписываюсь почти подо всем.
Но с однобуквенными переменными есть кое-какое рациональное зерно:
Сейчас почти каждый редактор умеет в пакетное выделение через ctrl+D.
И поленившись добавить вторую букву import unittest as t вы обрекаете себя на мытарства в дальнейшем. А стоило-то: import unittest as ut.


И, чтобы два коммента не писать:
почему f-strings рассматриваются в контексте html? Это отличный инструмент для производительного мелкого форматирования в одну строчку. через %-форматирование можно наткнуться на некоторые проблемы.
Допустим, у меня в компиляторе бывает так: %some_array[<и чего сюда писать для форматирования?>].

Если не ошибаюсь, то сlass Some(object) и class Some совсем не одно и то же.


В третьем питоне — одно и тоже.

Только что попробовал pylint на первом попавшемся питоновом скрипте. У меня это крутой скрипт, полностью покрытый тестами. Т.к. это скрипт, а не программа, тесты прилагаются внутри самого скрипта (набор функций def test_foo... в конце файла).


При этом у меня была проблема — как использовать mock'и и другие вкусные вещи, если я их не хочу грузить для исполнения скрипта. Решение простое:


def setup():
    global pytest
    global mock
    global builtins
    import unittest.mock as mock
    import pytest
    import builtins

После чего pytest, mock и builtins можно использовать внутри тестов.


И догадайтесь, за что мне поставили -3 в pylint'е? Правильно, за то, что переменные не определены.


def test_get_scenario_none():
    with pytest.raises(AssertionError):
            get_scenario([])

script.py:473:13: E0602: Undefined variable 'pytest' (undefined-variable)


Грош цена этому линтеру, если он разумное не понимает.

разумное не понимает.

Вы же знаете, что за глобальные переменные в менее терпимые временами сжигали сразу?)

Если вы внимательно посмотрите, то поймёте, что это очень осмысленный приём. Я в setup() настраиваю среду для тестов таким образом, чтобы не засорять модуль тестовым хламом на время нормального исполнения. Если вы мне пожете предложить лучший вариант сохранить тесты и код в одном файле без загрязнения тесто-ориентированными import'ами самого кода, я буду крайне благодарен.

То есть мы можем сейчас обсуждать правильно это или нет, но уж точно это не «undefined variable». А причина проста — pylint пытается статически анализировать динамический язык, т.е. как линтер никуда не годится. (А если бы у меня инъекция производилась во внешнем модуле?)
Если вы внимательно посмотрите, то поймёте, что это очень осмысленный приём. Я в setup() настраиваю среду для тестов таким образом, чтобы не засорять модуль тестовым хламом на время нормального исполнения. Если вы мне пожете предложить лучший вариант сохранить тесты и код в одном файле без загрязнения тесто-ориентированными import'ами самого кода, я буду крайне благодарен.

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


А причина проста — pylint пытается статически анализировать динамический язык, т.е. как линтер никуда не годится.

На самом деле, pylint все вам правильно сказал. Он не знает, что функция setup выполняется как-то автоматически. А если еще представить ситуацию, когда pylint в самом деле бы выполнял код, то можно прийти к выводу, что он бы крушил CI воркеров направо и налево, так как почти каждое крупное приложение на питоне что-то да и сделает при запуске.

… Это скрипт. Он не «устанавливается» в систему, у него нет этапа сборки. Его нельзя импортнуть через entrypoints. Он не является модулем. Таким образом, класть рядом тесты и танцевать с путями для импорта будет крайне неудобно. Можете считать частью ТЗ — тесты и код в одном файле.

pylint просто пытается делать вещи, которые делать не должен. В отличие от flake'а, который в нормальном режиме вполне себе адекватное требует, pylint требует неадекватного — чтобы «ему объяснили что там происходит».

Имена
Вы упускаете то, что в контексте почти всегда понятно, что такое result, data и так далее. В приведенном примере тоже не сильно понятно, что такое first_name и last_name, и почему они так странно лежат в словаре.


Называть переменные одной буквой — нормально?
index вместо i просто обмажет вас контекстом с ног до головы.


Консистентность
Я приведу в пример кусок кода, что бы проиллюстировать состоятельность тезисов в этом пункте (реальный код с реального проекта)


class GetMethodProcessApiView(general.ApiView):
    """
    view for get method
    """

    def get(request):
        """
        Perform get method processing
        """
        # get method processing

Зачем писать больше одного раза?


F-строки ужасны?
Я как-то попробовал запихнуть цикл в f-строку, как то не очень получается.


Используете ли вы ’@staticmethod’?
А как в противном случае группируете функции? Пихаете все в объекты или же чисто храните в модулях?


Функция hasattr часто вам нужна?


То есть вместо такого:


def _correct_compare(arg1, arg2) -> bool:
    if hasattr(arg1, '__corrupted_eq_check__'):
        return arg1._equals(arg2)
    return arg1 == arg2

мне писать


def _correct_compare(arg1, arg2) -> bool:
    try:
        if arg1.__corrupted_eq_check__:
            return arg1._equals(arg2)
    except AttributeError:
        return arg1 == arg2

А вместо


def _type_check(value: Any, target_type: Type) -> bool:
    if hasattr(target_type, '__args__'):
        if target_type.__origin__ is Union:
            return any(_type_check(value, x) for x in target_type.__args__)
        checking_type = target_type if not hasattr(target_type, '_gorg') else target_type._gorg
        if sys.version[0:3] == '3.7':
            base_result = isinstance(value, checking_type.__origin__)
        else:
            base_result = isinstance(value, checking_type)
        # TODO: process advanced type checking!
        return base_result
    return isinstance(value, target_type)

получится


def _type_check(value: Any, target_type: Type) -> bool:
    try:
        if target_type.__origin__ is Union:
            return any(_type_check(value, x) for x in target_type.__args__)
        try:
            checking_type = target_type._gorg
        except AttributeError:
            checking_type = target_type
        if sys.version[0:3] == '3.7':
            base_result = isinstance(value, checking_type.__origin__)
        else:
            base_result = isinstance(value, checking_type)
        # TODO: process advanced type checking!
        return base_result
    except AttributeError:
        return isinstance(value, target_type)

На мой взгляд, так выглядит немного хуже. И опять же, а зачем?

Я понимаю, что линтер суров, но мне он ругнулся на вот это:


        with open(config_path, 'r') as f:
            self.conf = yaml.load(f)

У меня есть острое ощущение, что with open(file) as f — очень даже идиоматичненько и не требует раскрытия смысла 'f'. Или требует?

sobolevn А как вы подружили bandit и pytest? Не помечать же каждый assert комментом # nosec.

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

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

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

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

Этот неприятный эффект рассинхронизации описания и исполняемой сущности приводит к обнаружению действия законов сохранения на функциональность. Принцип Дирихле — одна из реализаций законов сохранения в теории множеств, аналогично в физике и химии. В экономике действует этот же принцип: подделка билетов банка — область УК. А для функциональности я этот закон сформулировал как принцип требуемых качеств: воспользоваться в системе можно лишь тем, что в ней реализовано.

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

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

Почему не используется аналог CSS для оформления кода? Только потому что редактор кода не умеет? Это не принципиально: его же кто-то сделал, значит его можно научить или исправить. И она будет применяться в момент отображения кода без его непосредственного преобразования. В этом случае каждый программист может использовать свою индивидуальную систему стилей. Таким образом, холивары на тему отступов пробелами или табами (вместо интервала в mm или em), наименований, расположения запятых, оформления переменных и аргументов оказываются абсолютно беспочвенны: каждому свой вариант, свои правила, как индивидуально пошитые одежда и обувь!

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

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

Напомню: технологии предназанчены для того, чтобы снять стандартные операции и процессы с человека, высвободив его интеллектуальный потенциал под более сложные задачи, не охваченные технологией, т.е. не имеющие методов решений, протоколов, процессов и т.д. И, учитывая это предназначение, у меня вопрос: а что это за повальная дискриминация профессиональных и даже более искушённых пользователей? Т.е. что за мода к примитивизации и игнорированию нюансов? Это ведь ИТ и вычислительные устройства: можно делать куда больше и обрабатывать все случаи тем же, а то и меньшим объёмом кода!

Для того и применяются абстракции, интерфейсы, декомпозиция, протоколы, спецификации и иные методы обработки сложности, чтобы одним действием решать и делать куда больше одновременно! И если кому-то кажется, что это неправильно, то ему придётся признать, что природа оказалась куда практичнее него: в биологических организмах одновременно действующая функциональность одного и того же компонента — основной способ её распределения по носителю. И объём этой функциональности на порядки больше, чем в среднем, методов у класса. А если добавить заменяемость, самовосстанавливаемость, механизмы регуляции, то современные ИТ-системы — просто вирусы по сравнению млекопитающими.

Почему и откуда опять тот же кондовый совковый подход, вынужденно применяемый к негибким средствам реализации и исполняющим устройствам? Кто-то экономит свой мозг ценой перекладывания всего водопада последствий на пользователей и техподдержку. "… это очень большая проблема и так делать не надо" Т.е. вот такое размножение проблем — сущее зло: назначение ИТ — высвобождать время массового пользователя ценой однократной работы разработчика ИТ-средств, а не утилизировать это время вникуда под прикрытием чем-либо. Где линтеры на вот эти, куда более практически важные проблемы? Или занимаемся тем, чем можем и понимаем, а не тем, что требуется? Возможность эволюции системы обеспечивается не линтерами и рефакторингом, а изначально корректной декомпозицией устройства вещей и многообразия по принципу минимизации зависимостей и максимизации вариантов комбинирования.

Откуда взялась необходимость чтения кода, ведущая к борьбе с симптомами нехватки искусства программирования посредством линтеров? Чтобы изучить реализацию с целью изучения реализуемых в нём методов и путей его вывода. Т.е. вместо того, чтобы написать спецификацию, выраженную требованиями и ссылками к конкретному реализуемому методу, описанному на более высоком формальном языке, эти сведения пытаются выразить через код (относящийся к уровню реализации) и затем сообщить их неизвестному читателю. И что? Как? Хорошо работает?

Ни у кого не возникает ощущения, что пытаются использовать ненадлежащие методы и средства коммуникации и обучения? Что спецификация — не локальная документация к строчке кода, а код более высокого уровня, объект реализации? Потому что в исполняющем устройстве он не реализован, иначе кто-то решает уже решённую задачу. Конечно, второе имеет смысл в образовательных и тренировочных целях. Однако речь об эксплуатации исполняющего устройства вместе с целевым кодом, в первую очередь. А методам вычислительной науки, не говоря уже о методах предметных областей применения ИТ, учат несколько более обстоятельно и иначе, нежели отсылкой к некоторому программному коду, как варианту их реализации. Один из моих любимых примеров — минимизация конечных автоматов: по коду понять один из методов этой темы бывает крайне затруднительно, не говоря уже о доказательстве корректности и иных свойств метода типа вычислительной сложности. А без них метод ничего не стоит.

Основная мотивация борьбы с тем, что некто считает сложным — экономия его мозга, его энергии. Однако как её отличить от нежелания учиться, разбираться, выяснять, загружать и выгружать элементы контекста, переключаться? Это же наглая попытка опустить более способных до собственного уровня. Вот так нельзя: наоборот, это менее способные обязаны расти до уровня более способных, и в отличие от мышц, для мозга подобных ограничений нет. Иначе торжество примитивности ради индивидуальной понятности и простоты перейдёт в системную деградацию, и в терминальной стадии программный код уже никому не будет нужен, потому что никто не сможет заплатить за него своим трудом, в первую очередь.

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

История с наследованием композицией — вообще маразм, если посмотреть на классы на уровне исполняемого кода и содержимого оперативной памяти. Оба — методы сборки класса из возможностей/фич/особенностей. Тогда какая разница, каков порядок и способ сборки, если на выходе всё равно имеем множество фич как сущность/определение класса? А тогда ограничение «final type» — это самовольная блокировка возможности произвольной сборки множества фич. Видимо, чтобы создать проблему рассинхронизации, когда контейнер отстал от версии инкапсулированного класса. Нужна привязка к конкретной версии — ну так её и нужно применять тогда. А иначе предоставить пользователю возможность автоматического расширения интерфейса и функциональности при развитии родительских классов, т.е. чтобы не нужно было копировать интерфейс родительских классов и реализовывать тупым редиректом к методам объекта инкапсулированного класса. Т.е. вот так, ради идеи доминирования над другими, пользователям создаются подставы, запрещая наследования в целях сборки и обеспечения стандартного интерфейса с нулевой ценой поддержки. Границы там, где их проводят, т.е. в голове: реальности они не нужны, и, вероятно, не существуют даже, т.к. атомы — это конструкция из электромагнитных полей и иных сущностей без границ, в конечном счёте.

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

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

Мощный коммент!

В целом, идея форматировать код при отображении мне лично очень зашла. Спасибо.

Однако хочу отметить пункт про "наследование композицией" - не думаю, что это стилистическая проблема. Здесь идёт речь о выборе между статическим либо динамическим связыванием, собственно, о чём прямо и говорит Гамма сотоварищи (Gamma et. al, 19). Это выбор между гибкостью во время выполнения либо надёжностью во время компиляции. Однако "наследование композицией" требует крайне точного (и тонкого) определения интерфейсов, что на практике достигается редко, и требует куда большей компетенции. И как всегда, этот выбор вы должны сделать сами, основываясь на своих собственных "за и против". Если же навязывание того или иного выбора продиктовано кем-то или чем-то, то мы спускаемся на уровень догмы, о чём, собственно, вы сами и пишите в своём комментарии.

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

Type-check:

  • pyright. От microsoft, достаточно быстрый (в 100 раз быстрее, чем mypy) и, говорят, его проверки круче

  • pylyzer. На очень ранней стадии. Быстрее pyright в 4 раза ИЛИ в 400 раз быстрее, чем mypy. Он использует прям очень сложный механизм под капотом: он переводит Python в другой язык программирования, смотрит warning-и компилятора и переводит их в красивые warning-и пригодные для исходного кода

  • pytype, просто есть. я его не использую. он слишком медленный для меня.

Забавно, что свои чекеры есть от Facebook (ну или Meta) - pyra, от Microsoft - pyright и от - Google pytype. Все они жёстко поддерживаются родительскими компаниями, и все собираются жить вечно

Самым крутым (быстрым) линтером best-practices по-моему считается ruff, он, например, из коробки он проверяет сложность, как и wemake-services. Автору наверное не понравится, но ruff ругает когда использует .format(), советует использовать f-строки, но это легко кастомизируется

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

Они рекурсивные type hints, как свежий mypy, поддерживают?

Pylyzer - нет, pyra и pyright - да, pytype не знаю - не пользуюсь..

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