Должен ли я использовать искажение имен в Python?
В других языках общее правило, помогающее создавать лучший код, - всегда делать все как можно более скрытым. Если вы сомневаетесь в том, должна ли переменная быть частной или защищенной, лучше выбрать private .
Справедливо ли то же самое для Python? Должен ли я сначала использовать два начальных символа подчеркивания во всем и делать их менее скрытыми (только один символ подчеркивания) только по мере необходимости?
Если соглашение заключается в использовании только одного подчеркивания, я также хотел бы знать обоснование.
Вот комментарий, который я оставил к ответу Джбернардо. В нем объясняется, почему я задал этот вопрос, а также почему я хотел бы знать, чем Python отличается от других языков:
Я изучаю языки, которые учат вас думать, что все должно быть общедоступным только настолько, насколько это необходимо, и не более. Причина в том, что это уменьшит зависимости и сделает код более безопасным для изменения. Способ Python делать все наоборот - начиная с public и переходя к hidden - кажется мне странным.
Переведено автоматически
Ответ 1
Если сомневаетесь, оставьте это "общедоступным" - я имею в виду, не добавляйте ничего, что могло бы скрыть имя вашего атрибута. Если у вас есть класс с некоторым внутренним значением, не беспокойтесь об этом. Вместо того, чтобы писать:
class Stack(object):
def __init__(self):
self.__storage = [] # Too uptight
def push(self, value):
self.__storage.append(value)
запишите это по умолчанию:
class Stack(object):
def __init__(self):
self.storage = [] # No mangling
def push(self, value):
self.storage.append(value)
Это наверняка спорный способ ведения дел. Новички в Python ненавидят это, и даже некоторые старые ребята из Python презирают это значение по умолчанию - но это все равно значение по умолчанию, поэтому я рекомендую вам следовать ему, даже если вы чувствуете себя некомфортно.
Если вы действительно хотите отправить своим пользователям сообщение "К этому нельзя прикасаться!", обычный способ - поставить перед переменной один знак подчеркивания. Это всего лишь соглашение, но люди понимают это и проявляют двойную осторожность, когда имеют дело с такими вещами:
class Stack(object):
def __init__(self):
self._storage = [] # This is ok, but Pythonistas use it to be relaxed about it
def push(self, value):
self._storage.append(value)
Это также может быть полезно для предотвращения конфликта между именами свойств и именами атрибутов:
class Person(object):
def __init__(self, name, age):
self.name = name
self._age = age if age >= 0 else 0
@property
def age(self):
return self._age
@age.setter
def age(self, age):
if age >= 0:
self._age = age
else:
self._age = 0
Как насчет двойного подчеркивания? Ну, мы используем магию двойного подчеркивания в основном чтобы избежать случайной перегрузки методов и конфликтов имен с атрибутами суперклассов. Это может быть довольно полезно, если вы пишете класс, который нужно расширять много раз.
Если вы хотите использовать это для других целей, вы можете, но это не является ни обычным, ни рекомендуемым.
РЕДАКТИРОВАТЬ: Почему это так? Ну, обычный стиль Python не подчеркивает приватность вещей - наоборот! Для этого есть много причин, большинство из них противоречивы... Давайте рассмотрим некоторые из них.
У Python есть свойства
Сегодня большинство OO-языков используют противоположный подход: то, что не должно использоваться, не должно быть видимым, поэтому атрибуты должны быть закрытыми. Теоретически, это дало бы более управляемые, менее связанные классы, потому что никто не стал бы опрометчиво изменять значения объектов.
Однако это не так просто. Например, в классах Java есть много методов получения, которые только получают значения и установщиков, которые только устанавливают значения. Вам нужно, скажем, семь строк кода, чтобы объявить один атрибут, который программист на Python назвал бы излишне сложным. Кроме того, вы пишете много кода, чтобы получить одно общедоступное поле, поскольку на практике вы можете изменить его значение, используя методы получения и установки.
Итак, зачем следовать этой политике конфиденциальности по умолчанию? Просто сделайте свои атрибуты общедоступными по умолчанию. Конечно, в Java это проблематично, потому что, если вы решите добавить некоторую проверку к своему атрибуту, это потребует от вас изменения всех:
person.age = age;
в вашем коде, скажем,
person.setAge(age);
setAge()
будучи:
public void setAge(int age) {
if (age >= 0) {
this.age = age;
} else {
this.age = 0;
}
}
Итак, в Java (и других языках) по умолчанию в любом случае используются геттеры и сеттеры, потому что их написание может раздражать, но может сэкономить вам много времени, если вы окажетесь в ситуации, которую я описал.
Однако вам не нужно делать это в Python, поскольку у Python есть свойства. Если у вас есть этот класс:
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
... и тогда вы решите проверить возраст, вам не нужно изменять person.age = age
фрагменты вашего кода. Просто добавьте свойство (как показано ниже)
class Person(object):
def __init__(self, name, age):
self.name = name
self._age = age if age >= 0 else 0
@property
def age(self):
return self._age
@age.setter
def age(self, age):
if age >= 0:
self._age = age
else:
self._age = 0
Предположим, вы можете это сделать и все еще используете person.age = age
, зачем вам добавлять приватные поля, геттеры и сеттеры?
(Также см. Python - это не Java и эта статья о вреде использования геттеров и сеттеров.).
Все равно все видно - и попытка скрыть усложняет вашу работу
Даже в языках с закрытыми атрибутами вы можете получить к ним доступ через некоторую библиотеку отражения / самоанализа. И люди часто делают это в фреймворках и для решения неотложных задач. Проблема в том, что библиотеки самоанализа - это просто сложный способ делать то, что вы могли бы делать с общедоступными атрибутами.
Поскольку Python - очень динамичный язык, добавлять эту нагрузку к вашим классам контрпродуктивно.
Проблема в том, что ее невозможно увидеть - ее требуется увидеть
Для питониста инкапсуляция - это не невозможность видеть внутренности классов, а возможность не смотреть на это. Инкапсуляция - это свойство компонента, которое пользователь может использовать, не заботясь о внутренних деталях. Если вы можете использовать компонент, не беспокоясь о его реализации, то он инкапсулирован (по мнению программиста на Python).
Теперь, если вы написали класс, вы можете использовать его, не задумываясь о деталях реализации, нет проблем, если вы по какой-то причине захотите заглянуть внутрь класса. Суть в том, что ваш API должен быть хорошим, а остальное - детали.
Так сказал Гвидо
Ну, это не вызывает споров: он так и сказал, на самом деле. (Ищите "открытое кимоно".)
Это культура
Да, есть некоторые причины, но не критические. Это в первую очередь культурный аспект программирования на Python. Честно говоря, могло бы быть и по-другому - но это не так. Кроме того, вы могли бы так же легко спросить наоборот: почему некоторые языки используют приватные атрибуты по умолчанию? По той же основной причине, что и для практики Python: потому что такова культура этих языков, и каждый выбор имеет преимущества и недостатки.
Поскольку эта культура уже существует, мы настоятельно рекомендуем вам следовать ей. В противном случае вас будут раздражать программисты на Python, говорящие вам удалить __
из вашего кода, когда вы задаете вопрос в Stack Overflow :)
Ответ 2
Во-первых, Что такое искажение имен?
Искажение имен вызывается, когда вы находитесь в определении класса и используете __any_name
или __any_name_
, то есть два (или более) начальных символа подчеркивания и не более одного завершающего символа подчеркивания.
class Demo:
__any_name = "__any_name"
__any_other_name_ = "__any_other_name_"
А теперь:
>>> [n for n in dir(Demo) if 'any' in n]
['_Demo__any_name', '_Demo__any_other_name_']
>>> Demo._Demo__any_name
'__any_name'
>>> Demo._Demo__any_other_name_
'__any_other_name_'
Что делать, если сомневаетесь?
Предполагаемое использование заключается в том, чтобы запретить подклассам использовать атрибут, который использует класс.
Потенциальная ценность заключается в том, чтобы избежать конфликтов имен с подклассами, которые хотят переопределить поведение, чтобы функциональность родительского класса продолжала работать должным образом. Однако, пример в документации Python не является заменяемым по Лискову, и на ум не приходит примеров, где я нашел бы это полезным.
Недостатками являются то, что это увеличивает когнитивную нагрузку при чтении и понимании базы кода, особенно при отладке, когда вы видите имя с двойным подчеркиванием в исходном коде и искаженное имя в отладчике.
Мой личный подход заключается в том, чтобы намеренно избегать этого. Я работаю над очень большой базой кода. Редкие случаи его использования бросаются в глаза, как больной палец, и не кажутся оправданными.
Вам действительно нужно знать об этом, чтобы вы знали это, когда увидите.
PEP 8
В PEP 8, руководстве по стилю стандартной библиотеки Python, в настоящее время говорится (сокращенно):
Существуют некоторые разногласия по поводу использования
__names
.Если ваш класс предназначен для создания подклассов, и у вас есть атрибуты, которые вы не хотите использовать подклассами, рассмотрите возможность присвоения им имен с двойным подчеркиванием в начале и без подчеркивания в конце.
Обратите внимание, что в искаженном имени используется только простое имя класса, поэтому, если подкласс выбирает одно и то же имя класса и имя атрибута, вы все равно можете столкнуться с конфликтами имен.
Искажение имен может сделать некоторые виды использования, такие как отладка и
__getattr__()
, менее удобными. Однако алгоритм искажения имен хорошо документирован и его легко выполнить вручную.Не всем нравится искажение имен. Постарайтесь сбалансировать необходимость избегать случайных столкновений имен с потенциальным использованием продвинутыми абонентами.
Как это работает?
Если вы добавите два символа подчеркивания (без окончания двойными символами подчеркивания) в определении класса, имя будет искажено, а перед объектом будет добавлено подчеркивание, за которым следует имя класса:
>>> class Foo(object):
... __foobar = None
... _foobaz = None
... __fooquux__ = None
...
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']
Обратите внимание, что имена будут искажены только при анализе определения класса:
>>> Foo.__test = None
>>> Foo.__test
>>> Foo._Foo__test
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '_Foo__test'
Кроме того, у новичков в Python иногда возникают проблемы с пониманием того, что происходит, когда они не могут вручную получить доступ к имени, которое, по их мнению, определено в определении класса. Это не веская причина против, но это то, что следует учитывать, если у вас обучающаяся аудитория.
Один символ подчеркивания?
Если соглашение заключается в использовании только одного подчеркивания, я также хотел бы знать обоснование.
Когда я хочу, чтобы пользователи держали свои руки подальше от атрибута, я обычно использую только одно подчеркивание, но это потому, что в моей ментальной модели подклассы будут иметь доступ к имени (который у них всегда есть, поскольку они в любом случае могут легко определить искаженное имя).
Если бы я просматривал код, использующий префикс __
, я бы спросил, почему они вызывают искажение имен, и не могут ли они сделать так же хорошо с одним подчеркиванием, имея в виду, что если подклассы выбирают одинаковые имена для класса и атрибута class, то, несмотря на это, произойдет коллизия имен.
Ответ 3
Я бы не сказал, что практика приводит к улучшению кода. Модификаторы видимости только отвлекают вас от текущей задачи и в качестве побочного эффекта вынуждают использовать ваш интерфейс так, как вы задумали. Вообще говоря, обеспечение видимости не позволяет программистам все испортить, если они неправильно прочитали документацию.
Гораздо лучшим решением является маршрут, который поддерживает Python: ваши классы и переменные должны быть хорошо документированы, а их поведение понятным. Исходный код должен быть доступен. Это гораздо более расширяемый и надежный способ написания кода.
Моя стратегия в Python заключается в следующем:
- Просто напишите эту чертову штуку, не делайте предположений о том, как должны быть защищены ваши данные. Предполагается, что вы пишете для создания идеальных интерфейсов для ваших задач.
- Используйте начальный символ подчеркивания для материалов, которые вероятно не будут использоваться извне и не являются частью обычного интерфейса "клиентского кода".
- Используйте двойное подчеркивание только для вещей, которые являются чисто удобными внутри класса или могут нанести значительный ущерб, если их случайно раскрыть.
Прежде всего, должно быть ясно, что все делает. Задокументируйте это, если кто-то другой будет это использовать. Задокументируйте это, если вы хотите, чтобы это было полезно через год.
В качестве дополнительного примечания, на самом деле вам следует использовать protected в этих других языках: вы никогда не знаете, что ваш класс может быть унаследован позже и для чего он может быть использован. Лучше всего защищать только те переменные, которые, как вы уверены, не могут или не должны использоваться внешним кодом.
Ответ 4
Вы не должны начинать с личных данных и делать их общедоступными по мере необходимости. Скорее, вам следует начать с определения интерфейса вашего объекта. Т. е. вы должны начать с выяснения того, что видит мир (общедоступные материалы), а затем выяснить, какие частные материалы необходимы для того, чтобы это произошло.
Из-за других языков сложно сделать закрытым то, что когда-то было общедоступным. Т. е. Я нарушу много кода, если сделаю свою переменную закрытой или защищенной. Но со свойствами в python это не так. Скорее, я могу поддерживать тот же интерфейс даже с перестановкой внутренних данных.
Разница между _ и __ заключается в том, что python на самом деле пытается применить последнее. Конечно, он не очень старается, но это усложняет задачу. Наличие _ просто сообщает другим программистам, в чем заключается намерение, они могут игнорировать на свой страх и риск. Но игнорирование этого правила иногда полезно. Примеры включают отладку, временные взломы и работу со сторонним кодом, который не предназначался для использования так, как вы его используете.