object
В этом году анонсирован последний выпуск Python 2.7, после чего Python Software Foundation перестанет поддерживать ветку 2.7. Множество популярных библиотек и фреймворков тоже прекращают официальную поддержку Python 2, а целый ряд дистрибутивов Linux уже не включают его в набор пакетов по умолчанию.
Конечно, Python 2 не исчез из реальности, как только питоночасы пробили 00:00, но он уже стал темным прошлым, а не суровым настоящим. Начинающие могут смело знакомиться с Python 3 без оглядки на 2.7, что сильно упрощает жизнь.
Популярность Python привела к тому, что многие люди изучают его неформально: по чужим примерам и статьям в блогах. В таком методе нет ничего плохого, но есть риск упустить важные особенности поведения.
Сегодня мы сфокусируемся на объектной модели. Синтаксис создания классов и объектов в Python достаточно очевиден, а вот детали поведения — не всегда. Для опытных пользователей эта информация общеизвестна, но у тех, кто переходит от написания коротких скриптов и использования чужих классов к созданию своих приложений, порой вызывает сложности.
object
Начнем с самого простого случая — с классов, у которых нет явно указанного предка.
В Python 3 у любого пользовательского класса есть как минимум один базовый класс. В корне иерархии классов находится встроенный класс object
— предок всех классов.
В учебных материалах и коде часто можно видеть такую конструкцию:
class MyClass(object): pass
В Python 3 она избыточна, поскольку object
— базовый класс по умолчанию. Можно смело писать так:
class MyClass: pass
Популярность явного синтаксиса в коде на Python 3 связана с существовавшей долгое время необходимостью поддерживать обе ветки.
В Python 2.7 синтаксис MyClass(object)
был нужен, чтобы отличать «новые» классы от режима совместимости с доисторическими версиями. В Python 3 никакого режима совместимости с наследием старых версий просто не существует, поэтому наконец можно вернуться к более короткому старому синтаксису.
Что особенного в классе object
?
>>> o = object() >>> o.my_attribute = None Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'object' object has no attribute 'my_attribute'
«Техническая» причина этому — отсутствие у object
поля __dict__
, в котором хранятся все поля и методы класса.
Попытка изменить значение поля у объекта класса object
— это единственный случай, когда нечаянное или намеренное изменение атрибута уже созданного объекта завершится с ошибкой.
Во всех остальных случаях можно поменять любые атрибуты объекта и никаких ошибок это не вызовет — ошибки возникнут потом, когда в другом месте кода кто-то обратится к модифицированным полям.
>>> class MyClass: ... def my_method(self): ... print("I'm a method") ... >>> o = MyClass() >>> o.my_method = None >>> o.my_method() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'NoneType' object is not callable
Можно также добавить в уже созданный объект произвольные поля — класс объекта определяет, какие поля и методы у него будут сразу после создания, но никак не ограничивает, какие поля у него могут быть во время выполнения.
Казалось бы, инкапсуляция — один из трех фундаментальных принципов ООП, наравне с наследованием и полиморфизмом. Главное правило инкапсуляции — не давать пользователю объекта вносить в него не предусмотренные автором изменения. Однако даже без доступа к исходному коду достаточно упертый пользователь найдет способ модифицировать что угодно. Дух этого правила в другом: однозначно дать понять пользователю, где заканчивается стабильный публичный интерфейс и начинаются детали реализации, которые автор может поменять в любой момент.
Для этого в Python есть один встроенный механизм. Те поля и методы, которые не входят в публичный интерфейс, называют с подчеркиванием перед именем: _foo
, _bar
. Никакого влияния на работу кода это не оказывает, это просто просьба не использовать такие поля бездумно.
Для создания частных (private) атрибутов применяются два подчеркивания (__foo
, __bar
). Такие поля будут видны изнутри объекта под своими исходными именами, но вне объекта к ним применяется name mangling — переименования в стиле _MyClass__my_attribute
:
class MyClass: x = 0 _x = 1 __x = 2 def print_x(self): print(self.__x) >>> o = MyClass() >>> o.print_x() 2 >>> o.x 0 >>> o._x 1 >>> o.__x Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'MyClass' object has no attribute '__x' >>> o._MyClass__x 2
Как видишь, это все еще достаточно мягкое ограничение доступа — просто настойчивая просьба не использовать частные поля в обход публичного интерфейса объекта.
Переименование не применяется к полям и методам с подчеркиванием с двух сторон вроде __init__
. По соглашению такие имена дают «магическим методам», на которых построены все внутренние интерфейсы стандартной библиотеки Python: к примеру, o = MyClass()
— это эквивалент o = MyClass.__new__(MyClass)
. Такие методы, очевидно, должны быть доступны извне объекта под исходными именами.
Сэкономить время на создание публичного интерфейса к частным полям можно с помощью встроенного декоратора @property
. Он создает поле, которое выглядит как переменная только для чтения — попытка присвоить значение вызовет исключение AttributeError
. Для примера создадим класс с логическим значением _boolean_property
, которое можно поменять только методом set_property
, отклоняющим значения всех типов, кроме bool
.
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее
1 год7690 р. |
1 месяц720 р. |
Я уже участник «Xakep.ru»
Читайте также
Последние новости