В этом году анонсирован последний выпуск 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?

  1. У него самого нет базового класса.
  2. У объектов этого класса не только нет атрибутов и методов — нет даже возможности их присвоить.
>>> 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

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

INFO

Переименование не применяется к полям и методам с подчеркиванием с двух сторон вроде __init__. По соглашению такие имена дают «магическим методам», на которых построены все внутренние интерфейсы стандартной библиотеки Python: к примеру, o = MyClass() — это эквивалент o = MyClass.__new__(MyClass). Такие методы, очевидно, должны быть доступны извне объекта под исходными именами.

Сэкономить время на создание публичного интерфейса к частным полям можно с помощью встроенного декоратора @property. Он создает поле, которое выглядит как переменная только для чтения — попытка присвоить значение вызовет исключение AttributeError. Для примера создадим класс с логическим значением _boolean_property, которое можно поменять только методом set_property, отклоняющим значения всех типов, кроме bool.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Check Also

Вирус для Windows. Создаем простейшую вредоносную программу на ассемблере

Конструирование вирусов — отличный стимул изучать ассемблер. И хотя вирус, в принципе, мож…

1 комментарий

  1. Аватар

    Alexey Fedorov

    25.02.2020 at 20:50

    Простите, но о чем эта статья? Все описанные пункты прекрасно работают в python2.

Оставить мнение