1

В PEP8 сказано об именах:

double leading and trailing underscore: "magic" objects or attributes that live in user-controlled namespaces ... Never invent such names; only use them as documented.

При этом, я могу создать такой класс:

class A:
    def __str__(self):
        return 'classA'

    def __str1__(self):
        return 'classA_str1__'

    def __str2(self):
        return 'classA_str2'

Причём могу написать так:

print(a)
print(a.__str__())
print(a.__str1__())

Но не могу так:

print(a.__str2())

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

2 Answers2

4

Мне кажется, нужно разделить несколько частей вопроса:

  1. Почему .__method__ не ведет себя как .__method?

Просто по определению разработчиков языка. __method__ - довольно привычная конструкция, используется повсеместно для специальных методов типа str(), repr() и вообще множества операций, для любопытства можно вывести dir(1) или что-то подобное.

.__method() - лимитированная конструкция, используется редко и для избежания конфликтов в наследовании, см тут, фактически это не замена приватным методам в других языках.

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

В питоне приватный метод обозначается одним подчеркиванием _method(), но это просто синктаксическое соглашение, никаких особых функциональных отличий у него нет (может быть последовательность перечисления в вызове dir). Метод виден в dir, может вызываться. Прямого способа спрятать метод в питоне, как мне кажется, не предусмотрено.

Evgeny
  • 165
  • Спасибо! Выходит, с осмыслением синтаксиса у меня были проблемы. Тут Вы мне помогли, благодарю! Но конечной целью является создание именно специального метода. То есть, например, хотелось бы именно создать два метода _str_ и _specialStr_, которые возвращали бы строки, но разные. И при вызове print(A) вызывалась бы _st_, а при вызове specialPrint(A) - _specialStr_ –  Feb 07 '18 at 12:50
  • Не знаю, как вставлять подчёркивания в комментарий( Всё, что у меня выделилось жирным - оно было обрамлено двумя подчёркиваниями. –  Feb 07 '18 at 12:51
  • 2
    @Anton _str_ это \_str\_ . __str__ это \str`` – Andrio Skur Feb 07 '18 at 12:54
  • 1
    @Anton, можно как код в обратных кавычках: __str__ – MaxU - stand with Ukraine Feb 07 '18 at 12:54
  • 1
    Если я правильно понимаю вашу логику, вам нужна самостоятельная функция def specialPrint(x) - но она может быть связана с вашим классом как угодно, например def specialPrint(x): print(x._some_attribute) или то же самое с try-except. – Evgeny Feb 07 '18 at 12:55
  • Большое спасибо, @AndrioSkur! –  Feb 07 '18 at 12:56
  • Большое спасибо, @MaxU! –  Feb 07 '18 at 12:57
  • 1
    На практике для нового класса я бы советовал просто сделать свои __repr__() (более простой, ориентированный на повтор вызова конструктура) и __str__() (более симпатичный для печати). – Evgeny Feb 07 '18 at 12:58
  • Да, @EvgenyPogrebnyak, Вы правильно поняли, именно так. Просто возник интерес, можно ли сделать, чтоб её вызывать не как print(x._some_attribute), а как, ну например, print2(x)? –  Feb 07 '18 at 13:01
  • То есть, кажется, я что-то совсем искусственное придумываю, наверное, это и не надо никому, но тут уж любыпотно. –  Feb 07 '18 at 13:01
  • 1
    @Anton, главное, чтобы для вашей задачи это было нужно и укладывалось в конвенции языка. Можно и print2() написать и публичный метод .print2() в классе, но лучше идти от задачи и смотреть как это помогает в работе. Но корежить __этиметоды\_, имхо, лучше не надо ;) – Evgeny Feb 07 '18 at 13:05
  • @EvgenyPogrebnyak, всё, теперь вопросов не осталось) Большое спасибо Вам за объяснения! –  Feb 07 '18 at 13:08
  • 1
    Чтобы достучаться до __method, нужно указывать заместо __method, _ClassName__method. – sakost Feb 07 '18 at 13:24
  • 1
    @Anton: print(x) может вызвать специальные методы на типе x объекта, которые вы наиболее подходящим способом для вашей задачи можете определить. Чем отличается _repr_ от _str_? А может вам больше подойдет, переопределить sys.displayhook. Что происходит в интерактивном режиме (>>>) если ввести строку (или идентификатор проинициализированной переменной) и нажать Enter ? – jfs Feb 07 '18 at 13:53
1

Q: Но почему добавление двух подчёркиваний в конце имени метода позволяет вызывать его "снаружи" класса, даже тогда, когда, казалось бы, он должен быть приватным?

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

Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam[выделение жирным моё]


Q: Почему сказано не изобретать таких имён?

Потому что такие __special__ имена используются языком. Их много, могут появляться новые (e.g., __set_name__ в 3.6) — в принципе возможен конфликт. Главное: люди, читающие ваш код, ожидают, что подобные имена принадлежат языку. Поведение специальных методов (как они вызываются) отличается от обычных методов. Что может вести к тонким отличиям. К примеру, Python len() и .__len__() в чем разница?

Что свои протоколы вводить, __специальные__ имена или методы не нужны. К примеру, .quack(). Благодаря duck typing в Питоне, неявные протоколы во многих местах присутствуют, к примеру, print() может писать в "file-like" объект вместо sys.stdout. Иногда достаточно передать объект с одним write() методом. Некоторые устойчивые концепции (типа Hashable, Iterable) в виде ABC классов формализованы.

jfs
  • 52,361
  • Спасибо, это даже ещё точней! Именно то, что я хотел узнать. Вообще, спасибо за все ответы в разных вопросах, очень подробно и познавательно. И, конечно, восхищает уровень Ваших знаний. Кое-какие из ссылок я и сам видел, но не сообразил даже, что они связаны с моим вопросом. –  Feb 07 '18 at 14:53
  • А чтоб связывать вопросы, нужен некий определённый рейтинг? –  Feb 07 '18 at 14:57
  • 1
    @Anton: Спасибо, за добрые слова. Любая ссылка на другой Stack Overflow вопрос (в самом вопросе, ответе, комментарии) создаёт связанный вопрос. – jfs Feb 07 '18 at 15:18