Заметки об объектной системе языка Python ч.1

Несколько заметок об объектной системе python'a. Рассчитаны на тех, кто уже умеет программировать на python. Речь идет только о новых классах (new-style classes) в python 2.3 и выше. В этой статье рассказывается, что такое объекты и как происходит поиск атрибутов.


Объекты


Все данные в питоне — это объекты. Каждый объект имеет 2 специальных атрибута __class__ и __dict__.
  • __class__ — определяет класс или тип, экзмепляром которого является объект. Тип (или класс объекта) определяет его поведение; он есть у всех объектов, в том числе и встроенных. Тип и класс — это разные названия одного и того же. x.__class__ <==> type(x).
  • __dict__ словарь, дающий доступ к внутреннему пространству имен, он есть почти у всех объектов, у многих встроенных типов его нет.
Примеры.

>>> def foo(): pass
... 
>>> foo.__class__
<type 'function'>
>>> foo.__dict__
{}
>>> (42).__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__dict__'
>>> (42).__class__
<type 'int'>
>>> class A(object):
...     qux = 'A'
...     def __init__(self, name):
...         self.name=name
...     def foo(self):
...         print 'foo'
... 
>>> = A('a')

У a тоже есть __dict__ и __class__:

>>> a.__dict__   {'name': 'a'}
>>> a.__class__  
<class '__main__.A'>
>>> type(a)
<class '__main__.A'>
>>> a.__class__ is type(a)
True


Класс и тип — это одно и то же.

>>> a.__class__ is type(a) is A
True


a.__dict__ — это словарь, в котором находятся внутренние (или специфичные для объекта) атрибуты, в данном случае 'name'. А в a.__class__ класс (тип).

И, например, в методах класса присваивание self.foo = bar практически идентично self.__dict__['foo'] = bar или сводится к аналогичному вызову.

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

Пример. Переопределим класс объекта a:

>>> class B(object):
...     qux = 'B'
...     def __init__(self):
...         self.name = 'B object'
...     def bar(self):
 ...         print 'bar'
... 
>>> a.__dict__
{'name': 'a'}
>>> a.foo()
foo
>>> a.__class__
<class '__main__.A'>
>>> a.__class__ = B
>>> a.__class__
<class '__main__.B'>


Смотрим, что поменялось.

Значение a.name осталось прежним, т.е. __init__ не вызывался при смене класса.

>>> a.__dict__
{'name': 'a'}

Доступ к классовым переменным и методам «прошлого» класса A пропал:
>>> a.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'B' object has no attribute 'foo'

А вот классовые переменные и методы класса B доступы:
>>> a.bar()
bar
>>> a.qux
'B'


Работа с атрибутам объекта: установка, удаление и поиск, равносильна вызову встроенных функций settattr, delattr, getattr:

a.x = 1 <==> setattr(a, 'x', 1)
del a.x <==> delattr(a, 'x')
a.x <==> getattr(a, 'x')

При этом стоит стоит понимать, что setattr и delattr влияют и изменяют только сам объект (точнее a.__dict__), и не изменяют класс объекта.

qux — является классовой переменной, т.е. она «принадлежит» классу B, а не объекту a:

>>> a.qux
'B'
>>> a.__dict__
{'name': 'a'}


Если мы попытаемся удалить этот атрибут, то получим ошибку, т.к. delattr будет пытаться удалить атрибут из a.__dict__

>>> delattr(a, 'qux')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: qux
>>> del a.qux
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: qux
>>> a.qux
'B'
>>>


Далее, если мы попытаемся изменить (установить) атрибут, setattr поместит его в __dict__, специфичный для данного, конкретного объекта.

>>> = B()
>>> b.qux
'B'
>>> a.qux = 'myB'
>>> a.qux
'myB'
>>> a.__dict__
{'qux': 'myB', 'name': 'a'}
>>> b.qux
'B'
>>> 


Ну и раз есть 'qux' в __dict__ объекта, его можно удалить с помощью delattr:

>>> del a.qux

После удаления, a.qux будет возвращать значение классовой переменной:

>>> a.qux
'B'
>>> a.__dict__
{'name': 'a'}


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

Объекты и классы


Классы — это объекты, и у них тоже есть специальные атрибуты __class__ и __dict__.

>>> class A(object):
...     pass
... 


У класса тип type.

>>> A.__class__
<type 'type'>


Правда __dict__ у классов не совсем словарь

>>> A.__dict__
<dictproxy object at 0x1111e88>


Но __dict__ ответственен за доступ к внутреннему пространству имен, в котором хранятся методы, дескрипторы, переменные, свойства и прочее:

>>> dict(A.__dict__)
{'__module__': '__main__', 'qux': 'A', '__dict__': <attribute '__dict__' of 'A' objects>, 'foo': <function foo at 0x7f7797a25c08>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
>>> A.__dict__.keys()
['__module__', 'qux', '__dict__', 'foo', '__weakref__', '__doc__']<


В классах помимо __class__ и __dict__, имеется еще несколько специальных атрибутов: __bases__ — список прямых родителей, __name__ — имя класса. [1]

Классы можно считать эдакими расширениями обычных объектов, которые реализуют интерфейс типа. Множество всех классов (или типов) принадлежат множеству всех объектов, а точнее является его подмножеством. Иначе говоря, любой класс является объектом, но не всякий объект является классом. Договоримся называть обычными объектами(regular objects) те объекты, которые классами не являются.

Небольшая демонстрация, которая станет лучше понятна чуть позже.
Класс является объектом.
>>> class A(object):
...     pass
... 


>>> isinstance(A, object)
True


Число — это тоже объект.

>>> isinstance(42object)
True


Класс — это класс (т.е. тип).

>>> isinstance(A, type)
True

А вот число классом (типом) не является. (Что такое type будет пояснено позже)

>>> isinstance(42type)
False
>>>


Ну и a — тоже обычный объект.

>>> = A()
>>> isinstance(a, A)
True
>>> isinstance(a, object)
True
>>> isinstance(a, type)
False


И у A всего один прямой родительский класс — object.

>>> A.__bases__
(<type 'object'>,)


Часть специальных параметров можно даже менять:

>>> A.__name__
'A'
>>> A.__name__ = 'B'
>>> A
<class '__main__.B'>


С помощью getattr получаем доступ к атрибутам класса:

>>> A.qux
'A'
>>> A.foo
<unbound method A.foo>
>>> 


Поиск атрибутов в обычном объекте


В первом приближении алгоритм поиска выглядит так: сначала ищется в __dict__ объекта, потом идет поиск по __dict__ словарям класса объекта (который определяется с помощью __class__) и __dict__ его базовых классов в рекурсивном порядке.

Пример.

>>> class A(object):
...     qux = 'A'
...     def __init__(self, name):
...         self.name=name
...     def foo(self):
...         print 'foo'
... 
>>> = A()
>>> = A()


Т.к. в обычных объектах a и b нет в __dict__ атрибута 'qux', то поиск продолжается во внутреннем словаре __dict__ их типа (класса), а потом по __dict__ словарям родителей в определенном порядке:

>>> b.qux
'A'
>>> A.qux
'A'


Меняем атрибут qux у класса A. И соответственно должны поменяться значения, которые возвращают экземпляры класса A — a и b:

>>> A.qux='B'
>>> a.qux
'B'
>>> b.qux
'B'
>>> 


Точно так же в рантайме к классу можно добавить метод:

>>> A.quux = lambda self'i have quux method'
>>> A.__dict__['quux']
<function <lambda> at 0x7f7797a25b90>
>>> A.quux
<unbound method A.<lambda>>


И доступ к нему появится у экземпляров:

>>> a.quux()
'i have quux method'


Точно так же как и с любыми другими объектами, можно удалить атрибут класса, например, классовую переменную qux:

>>> del A.qux

Она удалиться из __dict__

>>> A.__dict__['qux']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'qux'


И доступ у экземляров пропадет.

>>> a.qux
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'qux'
>>> 


У классов почти такой же поиск атрибутов, как и у обычных объектов, но есть отличия: поиск начинается с собственного __dict__ словаря, а потом идет поиск по __dict__ словарям суперклассов (которые хранятся в __bases__) по опредленному алгоритму, а затем по классу в __class__ и его суперклассах. (Подробнее об этом позже).

Cсылки


  • Unifying types and classes in Python — главный документ, объясняющий что, как и зачем в новых классах.
  • Making Types Look More Like Classes — PEP 252, описывающий отличие старых классов от новых.
  • Built-in functions — детальное описание работы всех встроенных функций.
  • Data model — детальное описание модели данных python'а.
  • Python types and objects — объяснение объектной модели python на простых примерах с картинками.


Примечания


[1] О __module__ и __doc__ для простоты изложения пока забудем. Полный список атрибутов класса можно посмотреть в документации

Читать дальше


Заметки об объектной системе языка Python ч.2
Заметки об объектной системе языка Python ч.3
Метки:
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 34
  • +3
    мне кто-нибудь расскажет, в чем сакральный смысл передачи self в каждый метод класса? вот изучаю язык, но этого догнать не могу… все кажется, что костыль какой-то…
    • 0
      по аналогии с другими языками ($this например)
      • 0
        чтобы можно было явно получить указатель на конкретный экземпляр класса
        как-то так…
        • –1
          Инициализированный класс! Причем конкретный экземпляр в памяти.
          В __call__ он еще не инициирован.
        • +2
          Для лучшего понимания происходящего. В питоне нет никакой магии в классах — вместо неочевидных магических директив static и public все делается конструкциями языка, такими как декораторы @staticmethod и @classmethod, которые тоже ведут себя в рамках языка. Т.е. вы сами можете написать такие-же, например как здесь.

          Недавно в вопросы и ответы обращался человек который никак не мог понять, что происходит внутри ОПП. Когда я объяснил ему как выглядит ООП с точки зрения питона, он сразу понял. И судя по оценкам ответа, это объяснение показалось наиболее логичным не одному ему.
          • 0
            Да, но зато есть «магические» приставки "_" и "__" перед названиями методов :)
            • 0
              Это просто соглашение. «Магические» методы могли бы называться как угодно.
              • 0
                Я образно :) К тому, что есть немного неочевидные вещи.
            • 0
              А можно повторить?! А то, к сожалению уже не активно… Кто бы мог подумать каких-то 6 лет ))
          • +2
            >>> import this
            The Zen of Python, by Tim Peters
            ...
            Explicit is better than implicit.


            Если действительно хотите писать на этом прекрасном языке, изучите Дзен, хотя на это уйдёт не один день.
            И на последок: artifex.org/~hblanks/talks/2011/pep20_by_example.html
            • 0
              self — это указатель на все существующие и будущие экземпляры класса. Он передаётся как атрибут метода класса (всегда и явно) и обозначает что-либо в отношении экземпляра описанного класса. Фактически, при объявлении «self.name = name», вы как бы подразумеваете «имя_экземпляра.name, значение которого равно name». Не знаю корректно ли это звучит в русской терминологии, так как учил (и учу) язык по хорошим английским книгам.

              Все особенности языка замечательно и подробно описаны в книге «Learning Python» Марка Лутца. После её прочтения логично проработать «Programming Python» этого же автора. В конце концов, никакой мистики не останется.
              • +1
                Если я прафильно понимаю, то метод в питоне не более чем, аттрибут класса, который оказался объектом типа метод. Так что, вы вполне можете добавить к классу новый метод или скопировать из одного класса метод в другой. Чтобы, в таких ситуациях всё было очевидно, разработчики, видимо, решили не связывать метод с классом, которому он сейчас принадледжит, какими-то неявными механизмами, а передавать указатель в лоб, питом же стремится к ясности, пусть и в ущерб красоте.
              • 0
                Отличная статья, спасибо.
                Только сейчас понял что за тип type. :)
                • +1
                  Спасибо. Про type и object у меня отдельная заметка есть :)
              • +1
                >> Точно так же в рантайме к классу можно добавить метод

                Что самое весёлое, в python даже функции модуля и методу класса можно добавить состояние

                >>> def foo():
                … return 'foo'

                >>> foo()
                'foo'

                >>> foo.bar = 'bar'

                >>> foo.bar
                'bar'

                >>> foo
                <function foo at 0xb77f0224>

                >>> foo.__dict__
                {'bar': 'bar'}
                • 0
                  У Вас там нехватает атрибута qux в классе B.
                • +1
                  Хочу добавить, что начиная с Python 3, все классы изначально являются «new-style» и наследовать «object» не нужно.
                  • +1
                    Я думаю, пора уже писать статьи для третьей ветки или хотя бы с примечаниями для неё.

                    А ещё я никак не пойму сакральный смысл отступать от правил форматирования в укромных уголках кода типа:
                    self.name=name
                    • +1
                      Двойка, судя по всему, ещё несколько лет будет в строю. Хотя, казалось бы, уже достаточно времени прошло для перехода на тройку. Видимых отличий не так-то и много, скорее всего тормоз прогресса состоит в лени разработчиков. Django тот же всё никак не разродится, а жаль.

                      Про «правила форматирования» не совсем понял. Имеете ввиду отбивку знака равенства? Сам не знаю.

                      А вообще… www.python.org/dev/peps/pep-0008 2 раза в день после еды.
                      • +1
                        Это не «лень» называется. В том-то и дело, что отличий не так много, а портирование большой библиотеки на python 3 может быть очень нетривиальной задачей. Поэтому этим мало кто хочет заниматься — других задач и так куча, а тут, получается, вместо решения практических проблем тратить кучу сил, ломать совместимость (или внезапно начинать поддерживать несколько параллельных версий кода) — и все без особой практической выгоды.

                        Когда python 3 делали, это все прекрасно понимали. Изначально план был, что лет за 5 третья версия критическую массу наберет и вторую заменять начнет, так примерно все и идет.

                        Недавно еще wsgi тормозом был, сейчас вроде получше. А джангу Alex Gaynor летом собирается на py3k портировать.

                  • +1
                    Вы просто необычайно вовремя со своим циклом статей о ООП в пайтоне! Как раз заинтересовался этим необычным языком программирования и примерно сейчас взялся за ООП в нём.
                    Всё-таки я совершенно искренне огорчаюсь, что из-за правил Хабрахабра, человек с нулевой кармой не может высказать свою «материальную» благодарность автору, чьё изложение нужной темы в нужное время так помогло!
                    Позвольте, хотя бы на словах, от всего сердца поблагодарить Вас за этот цикл тем!
                    • 0
                      Спасибо. Рад стараться.
                    • 0
                      Столько плюсов статье и ни одного кмментария. Исправлю ситуацию.

                      Автору спасибо :) Видно, что он потратил кучу времени на эти статьи.
                      • +4
                        Как так? Матрица поимела меня :( Откуда вы все взялись только.
                      • 0
                        Тип и класс — это разные названия одного и того же.
                        Не вызывает доверия это утверждение что-то…
                        • 0
                          > Не вызывает доверия это утверждение что-то…

                          Ну собственно, чтобы сделать типы похожими на классы, а точнее все свести к одному, и были введены new-style classes, для которых это так и есть.

                          • 0
                            docs.python.org/reference/datamodel.html#new-style-and-classic-classes

                            «New-style classes were introduced in Python 2.2 to unify classes and types. A new-style class is neither more nor less than a user-defined type. If x is an instance of a new-style class, then type(x) is typically the same as x.__class__ (although this is not guaranteed — a new-style class instance is permitted to override the value returned for x.__class__).»
                          • –1
                            • 0
                              Спасибо тебе добрый человек! Отличная статья, многое из подсознательного выбралось в сознательное.
                              • 0
                                Отличная статья, только ссылки на остальные части не работают, поправьте пожалуйста.

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