Notes on the Python Object System Part 1

A few notes on the python object system. Designed for those who already know how to program in python. It is only about new classes (new-style classes) in python 2.3 and higher. This article describes what objects are and how attributes are searched.


The objects


All data in python are objects. Each object has 2 special attributes __class__ and __dict__.
  • __class__ - defines the class or type whose instance is an object. A type (or class of an object) determines its behavior; all objects, including built-in ones, have it. Type and class are different names for the same thing. x .__ class__ <==> type (x).
  • __dict__ is a dictionary that gives access to the internal namespace, almost all objects have it, many built-in types do not.
Examples. A also has __dict__ and __class__: Class and type are the same thing. a .__ dict__ is a dictionary containing internal (or object-specific) attributes, in this case 'name'. And in a .__ class__ class (type). And, for example, in class methods, the assignment self.foo = bar is almost identical to self .__ dict __ ['foo'] = bar or comes down to a similar call. There are no class methods, descriptors, class variables, properties, static class methods in an __dict__ object, all of them are defined dynamically using a class from the __class__ attribute, and are specific to the class (type) of the object, and not to the object itself. Example. Redefine the class of the object a: We look at what has changed.

>>> def foo(): pass
... 
>>> foo.__class__

>>> foo.__dict__
{}
>>> (42).__dict__
Traceback (most recent call last):
File "", line 1, in 
AttributeError: 'int' object has no attribute '__dict__'
>>> (42).__class__

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



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

>>> type(a)

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




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










>>> 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__

>>> a.__class__ = B
>>> a.__class__




The value of a.name remains the same, i.e. __init__ was not called when changing the class. Access to class variables and methods of the “past” class A disappeared: But class variables and methods of class B accesses: Working with object attributes: setting, deleting and searching is equivalent to calling the built-in functions settattr, delattr, getattr: ax = 1 <== > setattr (a, 'x', 1) del ax <==> delattr (a, 'x') ax <==> getattr (a, 'x') It should be understood that setattr and delattr affect they change only the object itself (more precisely, a .__ dict__), and do not change the class of the object. qux - is a class variable, i.e. it “belongs” to class B, and not to object a:

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


>>> a.foo()
Traceback (most recent call last):
File "", line 1, in 
AttributeError: 'B' object has no attribute 'foo'


>>> a.bar()
bar
>>> a.qux
'B'












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


If we try to remove this attribute, we get an error, because delattr will try to remove the attribute from a .__ dict__ Next, if we try to change (set) the attribute, setattr will put it in __dict__ specific to this particular object. Well, since there is a 'qux' in an __dict__ object, it can be deleted using delattr: After deleting, a.qux will return the value of the class variable: So:

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




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




>>> del a.qux



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



  • the class for the object is the value of the special attribute __class__ and it can be changed. (Although the official documentation says that there are no guarantees, but in fact it is possible)
  • almost every object has its own namespace (attributes), access (not always full), which is carried out using the special attribute __dict__
  • the class actually only affects the search for attributes that are not in __dict__, such as class methods, descriptors, magic methods, class variables, and more.

Objects and Classes


Classes are objects, and they also have special attributes __class__ and __dict__. The class has a type type. True __dict__ the classes are not quite a dictionary But __dict__ is responsible for accessing the internal namespace, which stores methods, descriptors, variables, properties and more: In the classes, in addition to __class__ and __dict__, there are several more special attributes: __bases__ - list of direct parents, __name__ Is the name of the class. [1]

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




>>> A.__class__




>>> A.__dict__




>>> dict(A.__dict__)
{'__module__': '__main__', 'qux': 'A', '__dict__': , 'foo': , '__weakref__': , '__doc__': None}
>>> A.__dict__.keys()
['__module__', 'qux', '__dict__', 'foo', '__weakref__', '__doc__']<




Classes can be considered some kind of extensions of ordinary objects that implement a type interface. The set of all classes (or types) belong to the set of all objects, or rather, is a subset of it. In other words, any class is an object, but not every object is a class. We agree to call regular objects those objects that are not classes.

A small demonstration that will become better understood a bit later.
The class is an object. A number is also an object. A class is a class (i.e. type). But the number is not a class (type). (What type will be explained later) Well, a is also an ordinary object. And A has only one direct parent class - object. Some of the special parameters can even be changed:
>>> class A(object):
...     pass
... 


>>> isinstance(A, object)
True




>>> isinstance(42, object)
True




>>> isinstance(A, type)
True



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




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




>>> A.__bases__
(,)




>>> A.__name__
'A'
>>> A.__name__ = 'B'
>>> A


Using getattr we get access to the class attributes:

>>> A.qux
'A'
>>> A.foo

>>> 


Search for attributes in a regular object


In a first approximation, the search algorithm looks like this: first it is searched in the __dict__ of the object, then it searches the __dict__ dictionaries of the class of the object (which is determined using __class__) and __dict__ of its base classes in recursive order.

Example. Because in ordinary objects a and b there is no 'qux' attribute in __dict__, then the search continues in the internal dictionary __dict__ of their type (class), and then on __dict__ parent dictionaries in a certain order: Change the qux attribute of class A. And accordingly, the values ​​should change, that return instances of class A - a and b: Similarly, in runtime, you can add a method to the class: And access to it will appear in the instances:

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




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




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




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




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


Just like with any other objects, you can remove the class attribute, for example, the qux class variable: It will be removed from __dict__ And access will be lost for instances. Classes have almost the same attribute search as ordinary objects, but there are differences: the search starts with our own __dict__ dictionary, and then we search the __dict__ dictionaries of superclasses (which are stored in __bases__) by a specific algorithm, and then by the class in __class__ and his superclasses. (More on this later).

>>> del A.qux



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




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




Links




Notes


[1] For the sake of simplicity, let us forget about __module__ and __doc__. The full list of class attributes can be found in the documentation.

Read more


Notes on the Python Object System Part 2
Notes on the Python Object System Part 3

Also popular now: