Приватные методы без нижнего подчеркивания и интерфейсы в Python



    Привет, Habr. Недавно угорел по дизайну — модификаторам доступа и интерфейсам, потом перенес это на язык программирования Python. Прошу под кат — делюсь результатами и как это работает. Для заинтересовавшихся в конце статьи есть ссылка на проект на Github.

    Модификаторы доступа


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

    private (приватные) методы доступны только внутри класса, protected (защищенные) — внутри класса и в дочерних классах.

    Как реализованы приватные и защищенные методы в Python


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

    class Car:
    
        def _start_engine(self):
            return "Engine's sound."
    
        def run(self):
            return self._start_engine()
    
    
    if __name__ == '__main__':
        car = Car()
    
        assert "Engine's sound." == car.run()
        assert "Engine's sound." == car._start_engine()
    

    Можно определить следующие минусы:

    • Если бы метод _start_engine обновлял какие-то переменные класса или сохранял состояние, а не просто возвращал «тупой расчет», вы могли что-то поломать для будущей работы с классом. Вы не позволяете себе что-то чинить в моторе вашей машины, потому что тогда никуда не поедете, верно?
    • Вытекающий пункт из предыдущего — чтобы убедиться, что можно «безопасно» (вызов метода не навредит самому классу) использовать защищенный метод — нужно заглянуть в его код и потратить время.
    • Авторы библиотек рассчитывают, что никто не пользуется защищенными и приватными методами классов, которые вы используете в своих проектах. Поэтому могут в любой релиз изменить его реализацию (которая на публичные методы не повлияет из-за обратной совместимости, но вы — пострадаете).
    • Автор класса, ваш коллега, рассчитывает, что вы не увеличите технический долг проекта, использовав защищенный или приватный метод вне созданного им класса. Ведь тому, кто будет его (приватный метод класса) рефакторить или изменять, придется убедиться (например, через тесты), что его изменения не поломают ваш код. А если поломают — ему нужно будет тратить время на то, чтобы решить эту проблему (костылем, потому что надо на вчера).
    • Возможно, вы следите за тем, чтобы другие программисты не использовали защищенные или приватные методам на code review и «бьете за это по рукам», значит — тратите время.

    Как реализовать защищенные методы с помощью библиотеки


    from accessify import protected
    
    
    class Car:
    
        @protected
        def start_engine(self):
            return "Engine's sound."
    
        def run(self):
            return self.start_engine()
    
    
    if __name__ == '__main__':
        car = Car()
    
        assert "Engine's sound." == car.run()
    
        car.start_engine()
    

    Попытавшись вызвать метод start_engine за пределами класса, вы получите следующую ошибку (метод недоступен согласно политике доступа):

    Traceback (most recent call last):
      File "examples/access/private.py", line 24, in <module>
        car.start_engine()
      File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/main.py", line 92, in private_wrapper
        class_name=instance_class.__name__, method_name=method.__name__,
    accessify.errors.InaccessibleDueToItsProtectionLevelException: Car.start_engine() is inaccessible due to its protection level
    

    Используя библиотеку:

    • Вам не надо использовать некрасивое (субъективно) нижнее или двойное нижнее подчеркивание.
    • Получаете красивый (субъективно) метод внедрения модификаторов доступа в код — декораторы private и protected.
    • Перекладываете ответственность с человека на интерпретатор.

    Как это работает:

    1. Декоратор private или protected — самый «высоко» расположенный декоратор, срабатывает до метода класса, которому объявили приватный или защищенный модификатор доступа.


    2. В декораторе с помощью встроенной библиотеки inspect достается текущий объект из стека вызовов — inspect.currentframe(). У этого объекта есть следующие полезные нам атрибуты: пространство имен (locals) и ссылка на предыдущий объект из стека вызова (объект, который вызывает метод с модификатором доступа).


      (Очень упрощенная иллюстрация)
    3. inspect.currentframe().f_back — используем этот атрибут, чтобы проверить, находится ли предыдущий объект из стека вызова в теле класса или нет. Для этого смотрим на пространство имен — f_locals. Если атрибут self в пространстве имен есть — метод вызывается внутри класса, если нет — вне класса. Если вызывать метод с приватным или защищенным модификатором доступа вне класса — будет ошибка политики доступа.

    Интерфейсы


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

    Пример


    Имеем класс User, который использует объект storage, чтобы создать нового пользователя.

    class User:
    
        def __init__(self, storage):
            self.storage = storage
    
        def create(self, name):
            return storage.create_with_name(name=name)
    

    Сохранять пользователя можно в базу данных, используя DatabaseStorage.create_with_name.

    class DatabaseStorage:
    
        def create_with_name(self, name):
            ...
    

    Сохранять пользователя можно в файлы, используя FileStorage.create_with_name.

    class FileStorage:
    
        def create_with_name(self, name):
            ...
    

    За счет того, что сигнатуры методов create_with_name (название, аргументы) у классов одинаковые — классу User не стоит волноваться какой объект ему подставили, если у обоих одинаковые методы. Это может быть достигнуто если классы FileStorage и DatabaseStorage реализуют одинаковый интерфейс (то есть связаны контрактом определить какой-то метод с логикой внутри).

    if __name__ == '__main__':
    
        if settings.config.storage = FileStorage:
            storage = FileStorage()
    
        if settings.config.storage = DatabaseStorage:
            storage = DatabaseStorage()
    
        user = User(storage=storage)
        user.create_with_name(name=...)
    

    Как работать с интерфейсами с помощью библиотеки


    Если класс имплементирует интерфейс, класс должен содержать все методы интерфейса. В примере ниже интерфейс «HumanInterface» содержит метод «eat», а класс «Human» его имплементирует, но не реализовывает метод «eat».

    from accessify import implements
    
    
    class HumanInterface:
    
        @staticmethod
        def eat(food, *args, allergy=None, **kwargs):
            pass
    
    
    if __name__ == '__main__':
    
        @implements(HumanInterface)
        class Human:
    
            pass
    

    Скрипт завершит работу со следующей ошибкой:

    Traceback (most recent call last):
      File "examples/interfaces/single.py", line 18, in <module>
        @implements(HumanInterface)
      File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator
        interface_method_arguments=interface_method.arguments_as_string,
    accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanInterface.eat(food, args, allergy, kwargs)
    

    Если класс имплементирует интерфейс, класс должен содержать все методы интерфейса, включая все входящие аргументы. В примере ниже интерфейс «HumanInterface» содержит метод «eat», который на вход принимает 4 аргумента, а класс «Human» его имплементирует, но реализовывает метод «eat» только с 1 аргументом.

    from accessify import implements
    
    
    class HumanInterface:
    
        @staticmethod
        def eat(food, *args, allergy=None, **kwargs):
            pass
    
    
    if __name__ == '__main__':
    
        @implements(HumanInterface)
        class Human:
    
            @staticmethod
            def eat(food):
                pass
    

    Скрипт завершит работу со следующей ошибкой:

    Traceback (most recent call last):
      File "examples/interfaces/single_arguments.py", line 16, in <module>
        @implements(HumanInterface)
      File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 87, in decorator
        interface_method_arguments=interface_method.arguments_as_string,
    accessify.errors.InterfaceMemberHasNotBeenImplementedWithMismatchedArgumentsException: class Human implements interface member HumanInterface.eat(food, args, allergy, kwargs) with mismatched arguments
    

    Если класс имплементирует интерфейс, класс должен содержать все методы интерфейса, включая входящие аргументы и модификаторы доступа. В примере ниже интерфейс «HumanInterface» содержит приватный метод «eat», а класс «Human» его имплементирует, но не реализовывает приватный модификатор доступа к методу «eat».

    from accessify import implements, private
    
    
    class HumanInterface:
    
        @private
        @staticmethod
        def eat(food, *args, allergy=None, **kwargs):
            pass
    
    
    if __name__ == '__main__':
    
        @implements(HumanInterface)
        class Human:
    
            @staticmethod
            def eat(food, *args, allergy=None, **kwargs):
                pass
    

    Скрипт завершит работу со следующей ошибкой:

    Traceback (most recent call last):
      File "examples/interfaces/single_access.py", line 18, in <module>
        @implements(HumanInterface)
      File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 77, in decorator
        interface_method_name=interface_method.name,
    accessify.errors.ImplementedInterfaceMemberHasIncorrectAccessModifierException: Human.eat(food, args, allergy, kwargs) mismatches HumanInterface.eat() member access modifier.
    

    Класс может имплементировать несколько (количество неограниченно) интерфейсов. Если класс имплементирует несколько интерфейсов, класс должен содержать все методы всех интерфейсов, включая входящие аргументы и модификаторы доступа. В примере ниже класс «Human» реализовывает метод «eat» интерфейса «HumanBasicsInterface», но не реализовывает метод «love» интерфейса «HumanSoulInterface».

    from accessify import implements
    
    
    class HumanSoulInterface:
    
        def love(self, who, *args, **kwargs):
            pass
    
    
    class HumanBasicsInterface:
    
        @staticmethod
        def eat(food, *args, allergy=None, **kwargs):
            pass
    
    
    if __name__ == '__main__':
    
        @implements(HumanSoulInterface, HumanBasicsInterface)
        class Human:
    
            def love(self, who, *args, **kwargs):
                pass
    

    Скрипт завершит работу со следующей ошибкой:

    Traceback (most recent call last):
      File "examples/interfaces/multiple.py", line 19, in <module>
        @implements(HumanSoulInterface, HumanBasicsInterface)
      File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator
        interface_method_arguments=interface_method.arguments_as_string,
    accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanBasicsInterface.eat(food, args, allergy, kwargs)
    

    Киллер фича — метод интерфейса может «заявить» какие ошибки должен «бросить» метод класса, который его имплементирует. В примере ниже «заявлено», что метод «love» интерфейса «HumanInterface» должен выбрасывать исключение «HumanDoesNotExistError» и
    «HumanAlreadyInLoveError», но метод «love» класса «Human» не «бросает» одно из них.

    from accessify import implements, throws
    
    
    class HumanDoesNotExistError(Exception):
        pass
    
    
    class HumanAlreadyInLoveError(Exception):
        pass
    
    
    class HumanInterface:
    
        @throws(HumanDoesNotExistError, HumanAlreadyInLoveError)
        def love(self, who, *args, **kwargs):
            pass
    
    
    if __name__ == '__main__':
    
        @implements(HumanInterface)
        class Human:
    
            def love(self, who, *args, **kwargs):
    
                if who is None:
                    raise HumanDoesNotExistError('Human whom need to love does not exist')
    
    

    Скрипт завершит работу со следующей ошибкой:

    Traceback (most recent call last):
      File "examples/interfaces/throws.py", line 21, in <module>
        @implements(HumanInterface)
      File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 103, in decorator
        class_method_arguments=class_member.arguments_as_string,
    accessify.errors.DeclaredInterfaceExceptionHasNotBeenImplementedException: Declared exception HumanAlreadyInLoveError by HumanInterface.love() member has not been implemented by Human.love(self, who, args, kwargs)
    

    Подводя итоги, с помощью библиотеки:

    • Можно имплементировать один или несколько интерфейсов.
    • Интерфейсы комбинируются с модификаторами доступа.
    • Вы получите разделение интерфейсов и абстрактных классов (модуль abc в Python), теперь не надо использовать абстрактные классы как интерфейсы, если вы это делали (я делал).
    • К сравнению с абстрактными классами. Если вы не определили все аргументы метода из интерфейса — получите ошибку, используя абстрактный класс — нет.
    • К сравнению с абстрактными классами. Используя интерфейсы, вы получите ошибку во время создания класса (когда вы написали класс и вызвали файл *.py). В абстрактных классах вы получите ошибку уже на этапе вызова метода объекта класса.

    Как это работает:

    1. В декораторе implements с помощью встроенной библиотеки inspect достаются все методы класса и его интерфейсов — inspect.getmembers(). За уникальный индекс метода принимается комбинация его имени и типа (staticmethod, property, и так далее).
    2. А с помощью inspect.signature() — аргументы метода.
    3. Проходим в цикле по всем методам интерфейса, и смотрим: есть ли такой метод (по уникальному индексу) в классе, который реализовывает интерфейс, одинаковые ли входящие аргументы, одинаковые ли модификаторы доступа, реализовывает ли метод объявленные ошибки в методе интерфейса.

    Спасибо за внимание к статье. Ссылка на проект на Github.
    Поделиться публикацией

    Похожие публикации

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

      +13
      Не очень понял, для кого это? Для тех, кто переходит с C++/Java/C# на питон, чтобы не травмировать психику?
        +2
        Спойлер — на уровне соглашения, что взрослые люди просто не будут их вызывать вне класса.

        Так в любом языке на уровне соглашениия. И в Java и в C# если калитка закрыта все равно можно влезть в огород через забор. (Рефлексия)
        Ничего не имею против, но есть же официальные соглашения, зачем переизобретать велосипед?)
          0
          И в Java и в C# если калитка закрыта все равно можно влезть

          Там эти соглашения контролирует компилятор и среда выполнения, «калитка» нетривиальна и серьезно отличается как синтаксически, так и семантически.
            +1
            Так в любом языке на уровне соглашениия. И в Java и в C# если калитка закрыта все равно можно влезть в огород через забор. (Рефлексия)

            Раз так, можно и подкоп устроить и вообще делать что захочется. (unsafe)

            Пример таких игрищ с одного доклада
            image


            Дело же не в том, можно или нельзя. Дело в том, что когда вызываешь проперти через точку, вряд ли возникнет ощущение, что ты делаешь какой-то хак, в отличии от рефлексии и unsafe. Все же простое нажатие клавиши на этапе компиляции это совсем не одно и то же, что и начать вызывать кучу методов или потрошить внутренности объектов.
              +2
              Когда вызываешь проперти через точку, вряд ли возникнет ощущение, что ты делаешь какой-то хак

              obj.__value

              Ну незнаю, когда видишь такие поля и методы не возникает же никаких подозрений? наверно что бы возникло ощущение, что ты делаешь какой-то хак нужно что-то такое?):
              obj.__dont_touch_it_if_you_dont_know_what_it_is_____value
                0
                Если без этого можно обойтись, зачем нужен этот синтаксический шум? В PHP с таким же успехом живет и здравствует бесполезный "$".
            0
            Применение интерфейсов не зависит от языка программирования — например, для реализации паттерна стратегия.

            С модификаторами доступа — чтобы не тратить время на то, чтобы пофиксить код, который использует приватный метод из твоего класса. Потому что он внезапно упал, когда ты изменил поведение метода.
              +11
              Надо бы уметь отключать это в runtime. Вызовы inspect достаточно дорогое удовольствие.
                +8
                Поражает как с завидной регулярностью выходцы из других ЯП пытаются привнести в уже состоявшийся язык особенности, к которым они привыкли и не хотят отвыкать.
                чтобы убедиться, что можно «безопасно» (вызов метода не навредит самому классу) использовать защищенный метод — нужно заглянуть в его код и потратить время

                А может просто не использовать защищенный метод? Он же потому защищенным и называется?
                Поэтому могут в любой релиз изменить его реализацию (которая на публичные методы не повлияет из-за обратной совместимости, но вы — пострадаете)

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

                Нет не тратим — такие вещи покрываются линтерами.
                Вообще, вся эта возня вокруг «защищенных» методов в питоне напоминает шутку про ограничивающую веревку перед пропастью — если ты был настолько туп что зашел за нее и упал — так тебе и надо.
                  +4

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

                    +11
                    Лично я считаю что в Python сделано как раз идеально. ДОступ к методам и атрибутам на уровне соглашений. Почему:
                    — Если метод не должен быть по мнению автора испольован вне класса, это сразу видно по имени.
                    — Если все-таки нужно вызвать/обратиться снаружи, разработчик легко может это сделать безо всяких извратов.
                    — Что в итоге получается — ответственность разработчика.
                    Язык скриптовый, исходники все равно лежат под рукой, все можно изменить. Зачем портить разработчику жизнь.
                    По моему так! © Винни-Пух
                      +2

                      "Если вы следите на код-ревью — вы тратите время". А если за этим следит библиотека при заходе в метод каждый раз — то она, видимо, время не тратит?
                      Как минимум, фича должна быть отключаемой.


                      Стоит помнить, что имя self — это конвенция. Никто не мешает писать методы, принимающие this или ещё что угодно.


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


                      Из метода класса можно или нельзя давать доступ к защищённым методам объектов данного класса, помимо self. То есть, мы даём или не даём привилегии разработчику класса?


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

                        +4

                        Но ведь можно просто использовать pylint.

                          +3
                          В начале, хочу похвалить. Получилась интересная статья — в качестве исследования возможностей языка. Но в продакшн я бы ничего из предложенного брать не стал. И вот почему:
                          Вас как видимо смутило, что в Python, которому через пару лет третий десяток стукнет, до сих пор нет реализации модификаторов доступа и интерйфесов в том виде, каком они сделаны в Java / C#.

                          И дело даже не в договорённости на уровне языка и его культуры.

                          Попытавшись вызвать метод start_engine за пределами класса, вы получите следующую ошибку (метод недоступен согласно политике доступа)

                          ОПА! А что нам даёт эта ошибка во время рантайма? В Java / C# это будет ошибка времени компиляции, а не выполнения.
                          С другой стороны тот же pylint спокойно отловит доступ к private/protected методам.
                          А с третьей — pylint тут и не нужен. Если программист по какой-либо причине использует приватные поля базового класса, или объекта — он должен полностью отдавать себе отчёт в том, что делает.

                          @implements(HumanInterface)

                          А как же утиная типизация? Есть у объекта метод __iter__ — значит по нему (скорее всего) можно итерироваться тем же for, нету — нельзя.

                          Чтобы не забыли? — вот тут соглашусь, это может быть удобно в больших проекта, но…
                          Pylint опять спешит на помощь и ловит неимплементированные методы абстракных классов!

                          @throws(HumanDoesNotExistError)
                          — a наколько глубока интроспекция? Может где-то в глубине метода я вызываю функции, которые эти исключения бросают? А может где-то вылетают и другие исключения?

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

                            Небольшой бенчмарк, показывающий замедление на порядок:


                            $ pipenv run python3 -mtimeit -s 'import regular as t; T = t.Test()' 'T.public()'
                            2000000 loops, best of 5: 153 nsec per loop
                            
                            $ pipenv run python3 -mtimeit -s 'import beauty as t; T = t.Test()' 'T.public()'
                            100000 loops, best of 5: 2.07 usec per loop

                            regular.py
                            class Test:
                                def public(self):
                                    return self._private()
                            
                                def _private(self):
                                    pass

                            beauty.py
                            from accessify import private
                            
                            class Test:
                                def public(self):
                                    return self.private()
                            
                                @private
                                def private(self):
                                    pass
                              +2
                              Людям с С++ и Java опытом, следует всегда помнить, что в Python вызов функции это дорогая операция.
                                +3
                                … как и в примерно любом другом интерпретируемом языке.
                              +2
                              Вообще, даже если оставить Python в стороне, эта фишка (защита полей в классах) в даже в C++ и Java мне не кажется полезной. ИМХО, типичный over-engineering (не знаю как по русски).
                                +1
                                «переусложнение». Модификаторы доступа (public/private & co) не нужны когда есть другой языковой механизм разделения интерфейса и реализации, без оверхеда. А разделение интерфейса и реализации необходимо, чтобы не плодились кривые костыли и грязные хаки.
                                  0
                                  Я бы перевел, как переусложнение без достаточных на то причин. Увлечение процессом ради самого процесса.
                                  0
                                  Согласен, в топку все поля, кроме приватных, а дальше пусть JIT разбирается.
                                  +1

                                  Поздравляю! Вы переизобрели zope.interface. Интерфейсы появились в Zope 15+ лет назад. Воистину, все новое — это забытое старое.


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


                                  Но до чего же невероятно выглядели Zope и Plone в свое время… Ничто другое даже рядом не стояло. И главное, я не уверен, что даже сейчас какая-нибудь CMS превосходит по возможностям Plone. А уж в во времена расцвета Plone казалось, что эта штука прямиком из далекого будущего.

                                    0
                                    Если атрибут self в пространстве имен есть — метод вызывается внутри класса, если нет — вне класса.

                                    А если метод вызывается в другом экземпляре этого класса? Будет выброшено исключение private метода?
                                      +4
                                      Как реализованы приватные и защищенные методы в Python
                                      Спойлер — на уровне соглашения, что взрослые люди просто не будут их вызывать вне класса. Перед приватными методами нужно писать двойное нижнее подчеркивание, перед защищенными одно. И вы по-прежнему можете обратиться к методам, несмотря на их «ограниченный» доступ.

                                      Это утверждение — в некоторой мере лукавство, которое упускает важную деталь (при учёте которой механизмы библиотеки уже не выглядят такими полезными).
                                      А деталь вот какая: если вы пишете двойное подчёркивание перед именем метода — это имя манглится и вы уже не сможете снаружи получить доступ напрямую к obj.__private. Случайно или по незнанию вызвать такой метод не получится.
                                      То есть это больше, чем просто соглашение.


                                      При этом, принимая во внимания, что получить доступ через obj._classname__private всё-таки можно, стоит учесть что это сделано для того, чтобы избежать перекрытия одинаковых названий функций при наследовании. И вызывая приватный метод таким образом вы сами сознательно подписываетесь, что собираетесь выстрелить себе в ногу. И это точно такой же хак, как и доступ к приватным полям в Java и C#.


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

                                        +2
                                        У вас Java головного мозга, скоро вы IoC в python притащите и AbstractFactory Factory.
                                        Описаный подход плох, тем что нарушает сразу несколько правил:
                                        — there is only one way to do it, теперь методы приватные могут быть и без подчеркивания
                                        — simple is better than complex, зачем тянуть библиотеку во все файлы да еще и добавлять строчку над каждым приватным методом.
                                        — convention over configuration, в питоне протектед методы начинаются с _ во всех 100500 батареек.

                                        На последок рекомедную прочитать про from abc import ABC

                                        that is no pythonic way.
                                          0

                                          Не вижу ничего плохого в IoC и DI, в частности (я не про IoC-контейнеры aka DI-фреймворки, если что)

                                          +2
                                          не надо в python тащить концепции из java/c#
                                            0

                                            По поводу интерфейсов не могу не напомнить, что в питоне утиная типизация. И хочу оставить ссылку на драфт PEP 544, зачатки которого уже есть в модуле typing.

                                              +1
                                              Забавно, что человек с бэкграундом статически типизированных языков хочет затащить в питон интерфейсы, но при этом полностью игнорирует type hint'ы.
                                                0
                                                  +1

                                                  Почему не использовать декоратор вместе с подчёркиванием? Как потом другой питонист будет обслуживать ваш код? Как я понял, декоратор вызывает экзепшн. Но нафига вы вместе с экзепшеном пытаетесь занести в язык кастомный код-стайл? Это в корне неверно. Оставьте этот декоратор — фиг с ним. Но не учите людей отказываться от соглашения. У нас и так достаточно библиотек, которые предлагают кастомный код-стайл. Та же джанга предлагает увеличивать допустимую длину строки до 100-120 cols. DRF в сериалайзерах допускает не имплементировать все методы родителя. Отдельные библиотеки настаивают на кэмл-кейсе в именах ф-ий. Теперь ещё учитывать этот наркоманский синтаксис без подчёркиваний на декораторах?!

                                                    0
                                                    Та же джанга предлагает увеличивать допустимую длину строки до 100-120 cols

                                                    А чем это плохо? Как быть программисту у которого монитор >= 27''? Страдать с 80 cols на четверти экрана? Поясните пожалуйста, как Вы работаете с этим соглашением?
                                                      0

                                                      Поясните, пожалуйста: для чего вам знать как я работаю с этим? А заодно, поясните: означает ли ваше высказывание, что можно не соблюдать соглашения, если у вас монитор на 27'?

                                                        +2

                                                        Получается, что вместо соглашения о длине строки мы получаем соглашение о размере монитора. Я прям представил contribution guide: минимальный размер монитора 27", не рекомендуется открывать два документа рядом

                                                          0
                                                          Python к этому планомерно двигается.
                                                          –1
                                                          для чего вам знать как я работаю с этим?

                                                          Почему у Вас возникли сложности ответить на простой вопрос? Я хочу знать аргументы, что заставляет вас придерживаться 80 cols и почему вы отказываетесь, например от 100-120 cols.


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

                                                          Конечно ДА, ведь это соглашение. С ним можно соглашаться, а можно не соглашаться. Выбор всегда остается за разработчиком. Ничто мне этому не препятствует (Python в Exception не вылетает).


                                                          Меня интересует почему Вы соглашаетесь с этим? Какие аргументы у этого соглашения? У вас монитор 15''? Или что? Можете аргументированно пояснить?

                                                            +1
                                                            1) Лично я люблю открыть в emacs несколько документов рядом, обычно 2.
                                                            2) Иногда приходится открывать в терминале и даже с телефона.
                                                            3) Ну и длинные строки провоцируют плохой стиль.
                                                            А тех, кто нарушает общепринятые соглашения, ИМХО, нужно публично расстреливать. Ну на первый раз отрубать мизинец, конечно. Но на третий, обязательно расстреливать!
                                                            Программирование на 70% держится на соглашениях.
                                                              –1

                                                              Ну первые 2 пунта еще куда не шло, можно принять за аргументы, но вот третий пункт… не аргументирован. Что значит плохой стиль? В чем его "плохость" проявляется? Конкретные примеры есть?


                                                              А тех, кто нарушает общепринятые соглашения, ИМХО, нужно публично расстреливать

                                                              Повеселили )) С большинством соглашений вполне можно согласиться, но вот это соглашения меня просто выбешивает и вынуждает создать аналогичное соглашение, чтобы производители мониторов не производили мониторы диагнональю больше 15'', а кто это соглашение будет нарушать, того, ИМХО:


                                                              нужно публично расстреливать

                                                              ))))

                                                                0
                                                                Причем тут мониторы вообще?
                                                                Если кто-то купил монитор, значит теперь весь мир должен под него подстраиваться?
                                                                Провоцирует пихать в код однострочники и всякие другие плохочитаемые штуки.
                                                                Я сторонник такого кода, при взгляде на который даже не читая особо сразы было бы понятно, что он делает.
                                                                В питоне еще позволяет запихать много отступов, (блоков кода), что тоже ОЧЕНЬ плохой стиль.
                                                                  0
                                                                  плохочитаемые штуки
                                                                  было бы понятно, что он делает.
                                                                  ОЧЕНЬ плохой стиль

                                                                  Это эмоции. Аргументов не увидел. Кому-то, например мне, непонятно что код делает, когда в нем присутствуют переносы. Это всё субъективность. Вам то нравится, мне это. Но объективных аргументов получается нет никаких?


                                                                  Причем тут мониторы вообще?

                                                                  При том, чтобы они ограничивали видимую длину строки и способствовали "хорошему" стилю программирования, соответствовали PEP'у.
                                                                  Какое это соглашение будет, хорошее или плохое? Чем оно будет принципиально оличаться от 80 cols?

                                                                    +1
                                                                    Нужно просто следовать соглашениям.
                                                                      –1
                                                                      Нужно просто следовать соглашениям.

                                                                      Обнаружило в PEP8 следующее:


                                                                      Many projects have their own coding style guidelines. In the event of any conflicts, such project-specific guides take precedence for that project.

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


                                                                      Так что, совершенно не обязательно следовать указанию в 80 cols. Тем более, этот PEP морально устарел, ему уже 18 лет. В 2001 году возможно это было актуально, чтобы код умещался на мониторе 14-15 дюймов которые в те времена были популярны.

                                                                        0
                                                                        Конечно не обязательно следовать ни PEP ни соглашениям. Даже если бы это не было бы написано.
                                                                        А что обязательно, так это пользоваться софтом написанным без следования общепринятым соглашениям и/или принимать в команду людей, которые не умеют следовать простейшим соглашениям.
                                                                        Это может сберечь много времени и нервов.
                                                                          –1
                                                                          принимать в команду людей, которые не умеют следовать простейшим соглашениям.

                                                                          Не понял, при чём тут люди какие-то, если речь шла о PEP? Люди нанимаются и обязаны следовать тому, что принято в команде/компании, а последння не обязана следовать PEP'у и может вполне "законно" создавать свои правила.


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


                                                                          Вы же их, почему-то хотите расстреливать ))

                                                                            0
                                                                            Вы же их, почему-то хотите расстреливать ))


                                                                            Я добрый, но тут без расстрелов не обойтись! :)
                                                                          +2

                                                                          PEP8 не является сводом законов. Вы правы. Но, если вы говорите, что только поэтому нужно не соблюдать соглашения, то вам лучше прекратить использовать пайтон. Наш уютный мир Python-программистов зиждется на соблюдении соглашений и на понятном code-style. К сожалению, нам приходится работать и с такими специалистами, которые считают, что правила существуют для того, чтобы их нарушать. Код таких специалистов не останется в истории. Мы его удаляем и переписываем. На собеседовании мы не пропускаем таких специалистов дальше. Поэтому, уважаемый, ваше существование в роли разработчика Python — бессмысленно. Раскидистая ива у реки оставит после себя столько же работоспособного кода, сколько и вы.

                                                                +1
                                                                Меня интересует почему Вы соглашаетесь с этим? Какие аргументы у этого соглашения? У вас монитор 15''? Или что? Можете аргументированно пояснить?


                                                                Любой программист, работающий в вашей команде, может использовать любой монитор: 11', 12', 15', 27', 32'. Без разницы! Вы должны думать, прежде-всего, об едином code-style, а не о причинах отказа от соглашений. К вам в команду может прийти сторонний специалист. Он должен знать основные паттерны и соглашения, чтобы понимать, что вы написали. Вы пишете код не для себя. Поэтому, соответствие соглашениям — это хороший тон.

                                                                Но размер монитора — это ещё не всё. Ограничения, которые накладывает та же длина строки, влияют на ваш подход. Например, в Django вы можете найти такие конструкции:

                                                                qs = MyModel.objects.exclude(field__isnull=True).filter(field1='payed').filter(Q(field2__gte=30) | Q(field3='free')).annotate(total=Sum('field4')).annotate(owner_email=F('owner__email')).order_by('-time_created')


                                                                Соответственно, если конструкция будет достаточно длинной, она потребует переноса. Поэтому, в Django допустимо делать и 90, и 100, и 120 строк. Людям не комфортно делать такие конструкции в 80 строк. Плюс, подобное ограничение требует рефакторинга. Например, мы можем собрать args, kwargs в отдельных переменных или написать кастомный QuerySet, в котором нам потребуется написать, скажем, def filter_payed, которая возьмёт на себя добавление .filter(field1='payed'). Это будет следствием того, какую длину строки мы выбрали: будет нам проще написать всё в одной строке, или задуматься о рефакторинге. Будет ли .filter(field1='payed') повторяемым элементом? Что если нет? В любом случае, ограничение длины строки предложит нам эту конструкцию куда-то убрать, чтобы избежать переносов. Либо, создать утилку, которая будет билдить такой QuerySet.

                                                                Длина строки ограничивает нас в создании длинных строк. Предлагает нам решать проблему длинных строк альтернативными методами. Поэтому, начиная проект или, присоединяясь к проекту, нам важно определить с каждым членом команды, какую длину строк мы используем? 80, 90, 100, 111? Нам важно, чтобы все с этим согласились. Есть люди, которые говорят: «ой, да посрать, тут короче 27' мониторы, блаблабла, я такой крутой спец, такие рассуждения имею на каждый счёт»! Короче, как вы. Вы тратите своё и чужое время, пытаясь доказать себе и другим, почему вы не можете сделать это быстро. Просто взять и принять соглашение, которое будет в команде. А в команде будет, скорее-всего, соглашение, которое уже где-то встречалось. Которое считается общепризнанным и известным. Никто не будет принимать соглашение одного гордого чувака, который пытается похвастаться своим 27' монитором. Его просто не примут в команду или попросят на выход — выпендриваться своим монитором в веб-студии с бесплатными печеньками. Потому, что соглашения влияют на код и требуют применять различные методики и практики. И эти методики должны быть доступны и понятны всем членам команды.
                                                                  +2
                                                                  Пожалуйста, не надо писать такое
                                                                  qs = MyModel.objects.exclude(field__isnull=True).filter(field1='payed').filter(Q(field2__gte=30) | Q(field3='free')).annotate(total=Sum('field4')).annotate(owner_email=F('owner__email')).order_by('-time_created')

                                                                  даже если ширина монитора позволяет.

                                                                  То же самое можно записать так
                                                                  qs = (MyModel.objects
                                                                        .exclude(field__isnull=True)
                                                                        .filter(field1='payed')
                                                                        .filter(Q(field2__gte=30) | Q(field3='free'))
                                                                        .annotate(total=Sum('field4'))
                                                                        .annotate(owner_email=F('owner__email'))
                                                                        .order_by('-time_created')
                                                                        )

                                                                  и с этим будет проще работать на любом мониторе.
                                                                    0

                                                                    Речь не о том, что надо писать такое. Я объяснял человеку, почему рекомендации по длине строк для Джанги отличаются от PEP8 и почему эти рекомендации не отменяют PEP8.

                                                                      –2

                                                                      Вы привели плохой пример. Не учли, что код может иметь значительное количество отступа по причине вложенности if'ов, while'ов, for'ов и т.д., где писать строку на оставшихся символах затруднительно и не всегда читабельно. А еще надо найти место комментариям. Автор, который Вас поправил — я с ним полностью согласен, написал бы так же, как он указал.

                                                                        0
                                                                        Какие ifы? Кто кого поправил? Вы в цирке в носатых туфлях работаете или что?
                                                                        +2
                                                                        если у вас в коде шестой/седьмой уровень вложенности, значит вы что-то делаете не так
                                                                      0

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

                                                                +3

                                                                На этот вопрос уже ответили, но я тоже дополню.


                                                                1. Действительно, удобно иметь возможность работать с двумя документами открытыми рядом (vertical split в редакторах/IDE)
                                                                2. Есть такая вещь как 3-way merge без которой часто не обойтись. Длинные строки превращают ее в невыносимый кошмар
                                                                3. Действительно, по-моему опыту, длинные строки приводят к плохому коду… И тяжело читаются даже на большом мониторе. Ведь, когда вы читаете сайт, вам тоже удобно, что строки текста не растянуты на весь экран, а имеют отступы слева и справа, и текст не более определенной ширины. С сайтов, которые используют длиннющие строки во весь экран сразу же хочется уйти, читать их очень сложно. Хорошо, что есть Reader Mode. Но для кода такого пока нет.
                                                                  0
                                                                  Ведь, когда вы читаете сайт, вам тоже удобно, что строки текста не растянуты на весь экран

                                                                  Речь не шла про неограниченную длину строки. Конечно, ограниченная строка удобнее, но есть объективные причины для увеличения строки (я использую 140 — очень комфортно на мониторе 27'' с разрешением 2k):


                                                                  • Разрешение монитора, размер шрифта, DPI
                                                                  • Диагональ матрицы монитора
                                                                  • Расстояние наблюдателя до монитора

                                                                  Этот пункт PEP'а, на мой взгляд явно устарел, но благо его не нужно оспаривать, перепринимать и вводить новый PEP, так как последний — это всего лишь рекомендация, а предпочтения автора являются приоритетней PEP'а согласно ему же, и в частности пункта про 80 cols.


                                                                  А вот другие пункты PEP'а вечны, их вполне можно и нужно соблюдать. У меня претензия только к 80 cols ))

                                                                    +1
                                                                    я использую 140 — очень комфортно на мониторе 27'' с разрешением 2k

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

                                                                      Люди разные бывают, под всех не подстроишься. Есть те, например, кто использует старый ЭЛТ монитор с 14 дюймами диагонали.


                                                                      Программы пишутся для других программистов

                                                                      Нет. Программист будет писать то и так, как пожелает автор проекта (автор может быть не только конкретное лицо, но и целая корпорация, которая не пишет "для других программистов", а устанавливает свои правила). В том же yapf можно выбрать разные стили от авторов, включая google-style которые не полностью соответствуют обсуждаемому PEP'у.

                                                                        0
                                                                        Программист будет писать то и так, как пожелает автор проекта

                                                                        Менеджер проекта напрямую заинтересован в том, чтобы проект был дешево поддерживаемым.
                                                                          0

                                                                          Разумеется, поэтому в гугл есть свои стили, отличные от "публичных" (широко принятых). И не только в гугл.

                                                                            0
                                                                            Окститесь, гугл целый язык запилил, чтобы программистам проще было жить. Для того же C++ их стайлгайд очень популярен в народе.
                                                                              0
                                                                              О чём и речь. Только не программист это решил, а гугл (автор).
                                                              +2
                                                              Любой мэинстрим язык в итоге превратиться в Java.
                                                                0
                                                                Это потому, что явистов очень много. А все дурное влияние ужасного монстра — C++.
                                                                  0
                                                                  А все дурное влияние ужасного монстра — C++

                                                                  Который вышел из недостатков C

                                                                0
                                                                Перед приватными методами нужно писать двойное нижнее подчеркивание, перед защищенными одно.


                                                                Почему многие в это слепо верят? У двойного подчеркивания совсем другая надобность.
                                                                  0

                                                                  Какая?

                                                                    0
                                                                    Это называется name mangling и нужно для разрешения конфликта имён при множественном наследовании, но никак не для декларации приватных методов. Для декларации внутренних методов используется только одинарное подчеркивание.
                                                                      0
                                                                      нужно для разрешения конфликта имён при множественном наследовании

                                                                      Нужно кому? Python'у? Или программисту? Почему он для разрешения имен не может использовать другие символы? Что-то я тут не понял. Можете привести простой пример с таким наследованием?

                                                                        –1

                                                                        Чтобы не было таких приколов, например.


                                                                        class Mage(object):
                                                                            def fire(self):         self.cast_flame()
                                                                            def smart_fire(self):   self.__smart_cast()
                                                                        
                                                                            def   cast_flame(self): print("Mage flame")
                                                                            def __smart_cast(self): print("Trully mage flame")
                                                                        
                                                                        class FlameDaemon(object):
                                                                            def fire(self):         self.cast_flame()
                                                                            def smart_fire(self):   self.__smart_cast()
                                                                        
                                                                            def   cast_flame(self): print("Daemon flame")
                                                                            def __smart_cast(self): print("Trully daemon flame")
                                                                        
                                                                        class Hooman(Mage, FlameDaemon):
                                                                            def fire(self, is_daemon=True):
                                                                                if is_daemon: Mage.fire(self)
                                                                                else:         FlameDaemon.fire(self)
                                                                        
                                                                        class SmartHooman(Mage, FlameDaemon):
                                                                            def fire(self, is_daemon=True):
                                                                                if is_daemon: Mage.smart_fire(self)
                                                                                else:         FlameDaemon.smart_fire(self)
                                                                        
                                                                        h = Hooman()
                                                                        h.fire(True)
                                                                        h.fire(False)
                                                                        
                                                                        h = SmartHooman()
                                                                        h.fire(True)
                                                                        h.fire(False)

                                                                        В первом случае даже когда вы вызываете метод из класса FlameDaemon вызывается метод из класса Mage, что, очевидно, совсем не то, что ожидается. Это происходит из-за того, что вы находитесь в объекте self класса Hooman/SmartHooman всегда, даже когда это базовый класс, а наследование происходит в порядке перечисления. Следовательно, self.cast_flame указывает на Mage.cast_flame т.к. он приоритетнее, а во втором случае имена манглятся и пересечения нет.

                                                                          0
                                                                          В первом случае даже когда вы вызываете метод из класса FlameDaemon вызывается метод из класса Mage, что, очевидно, совсем не то, что ожидается

                                                                          Вот это я не понял, почему не то, что ожидается? Ведь по MRO как раз таки то, что ожидается? Вы же self передаете в метод fire() объект Hooman, а MRO этого объекта = Hooman, Mage, FlameDaemon, builtins.object.


                                                                          а во втором случае имена манглятся и пересечения нет.

                                                                          Вот тут да, понятно стало. Спасибо за пример.

                                                                            0
                                                                            Вот это я не понял, почему не то, что ожидается? Ведь по MRO как раз таки то, что ожидается?

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


                                                                            Будет ли ожидаемо, читая (особенно чужой) код, что вызывая метод в одном из базовых классов, приходит в ответ какая-то чепуха?
                                                                            Как минимум, такой код не слишком тривиально отлаживать и поддерживать.

                                                                              0
                                                                              А… в этом смысле… Понятно.

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

                                                                Самое читаемое