How would you write these 2 classes as dictionaries?
class Base:
x = 10
class Derived(Base):
y = 20
For the first class the equivalent dictionary is:
Base = {"x": 10}
What about the second one?
How would you write these 2 classes as dictionaries?
class Base:
x = 10
class Derived(Base):
y = 20
For the first class the equivalent dictionary is:
Base = {"x": 10}
What about the second one?
If by "dictionary equivalent" you mean that classes and instances attributes have to be stored in a mapping, you are right. If you think that Python Data Model is simple as a dictionary, you are wrong.
There is a special attribute __dict__
for the mapping:
object.__dict__
A dictionary or other mapping object used to store an object’s (writable) attributes.
But you have to differentiate between class attributes and instance attributes:
>>> class A:
... class_attr = 5
... def __init__(self): self.instance_attr = 10
...
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'class_attr': 5, '__init__': <function A.__init__ at ...>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None})
>>> a = A()
>>> a.__dict__
{'instance_attr': 10}
As you see, class_attr
is in the mapping of the class A
, whereas instance_attr
is in the mapping of the instance a
.
You can go up from the instance to its class:
>>> a.__class__.__dict__
mappingproxy({'__module__': '__main__', 'class_attr': 5, '__init__': <function A.__init__ at ...>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None})
But Python can resolve class attributes calls from instance, even if the attribute is not in the mapping of the instance:
>>> a.class_attr
5
This is black magic!
Let's try your example:
>>> class Base:
... x = 10
...
>>> class Derived(Base):
... y = 20
...
>>> Base.__dict__
mappingproxy({'__module__': '__main__', 'x': 10, '__dict__': <attribute '__dict__' of 'Base' objects>, '__weakref__': <attribute '__weakref__' of 'Base' objects>, '__doc__': None})
>>> Derived.__dict__
mappingproxy({'__module__': '__main__', 'y': 20, '__doc__': None})
As you can see, the dicts are orthogonal because x
and y
are class instances. Thus, x
is not in the mapping of Derived
. Let's answer the question you asked: the Derived
equivalent dictionary is:
Derived: {"y": 10}
But:
>>> Derived.x
10
More black magic!
Let's try with instance attributes:
>>> class Base:
... def __init__(self):
... self.x = 10
...
>>> class Derived(Base):
... def __init__(self):
... self.y = 20
...
>>> b = Base()
>>> b.__dict__
{'x': 10}
>>> d = Derived()
>>> d.__dict__
{'y': 20}
But this time, it won't work as above:
>>> d.x
Traceback (most recent call last):
...
AttributeError: 'Derived' object has no attribute 'x'
You need to fix the Derived.__init__
method with an explicit call to its parent initializer:
>>> class Derived(Base):
... def __init__(self):
... super(Derived, self).__init__()
... self.y = 20
...
You get:
>>> d = Derived()
>>> d.__dict__
{'x': 10, 'y': 20}
>>> d.x
10
I hope you are convinced that instances, classes and dictionaries are quite different, although they share the concept of mapping. The main differences (that is the "black magic") are explained in the Python Data Model documentation and you can find some useful questions/answers on SO: What is getattr() exactly and how do I use it? for instance.
Bonus: you wrote in a comment that
class A:
def __init__(self):
self.x = 10
a = A()
print(a.x)
can be written as:
def init(obj):
obj["x"] = 10
A = {"init": init}
a = dict(A)
a["init"](a)
print(a["x"])
But that's a confusion between class and instance attribute, because init
is not an attribute of the instance a
, but of the class A
. You should have written:
A = {"init": init}
a = {}
A["init"](a)
print(a["x"])
Note that the line a = {}
has no visible equivalent in the A.__init__
method. When you write self.x = 10
, self
is already initialized. This is the job of the A.__new__
method:
>>> class A:
... def __new__(cls):
... o = super().__new__(cls) # this is the equivalent of a = {}
... cls.last_created = o # store the object for test below
... return o
...
... def __init__(self):
... self.x = 10
...
>>> A.last_created
Traceback (most recent call last):
...
AttributeError: type object 'A' has no attribute 'last_created'
>>> a = A()
>>> a == A.last_created
True