Pull to refresh

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

Reading time 8 min
Views 62K
Несколько заметок об объектной системе 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
Tags:
Hubs:
+93
Comments 35
Comments Comments 35

Articles