In [ ]:
%%HTML
<img src="https://imgs.xkcd.com/comics/bun_alert.png" width=500></img>
In [ ]:
%%HTML
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Pay no mind.... <a href="https://t.co/mnIPHJXE1h">pic.twitter.com/mnIPHJXE1h</a></p>— David Beazley (@dabeaz) <a href="https://twitter.com/dabeaz/status/890634046958477312">July 27, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
In [ ]:
# let's reproduce it
class A():
pass
A.__dict__ is A.__dict__
In [ ]:
# ... and more robustly...
a = A()
a.__class__.__dict__ is a.__class__.__dict__
The code in question involves class objects and instances, the is
operator, and attribute access via the dot notation. Let's explore how those objects and operations work.
Out of scope:
...but you will run into these concepts if you investigate beyond this tutorial.
In [ ]:
class B():
pass
In [ ]:
C = type('C',(),dict())
In [ ]:
D = type('C',(),dict())
In [ ]:
D
Takeaways:
Reminder:
object
, including objects that are class definitions
In [ ]:
# Start with the equivalence operator (==)
# --> remember that this will be defined by the ".__eq__()" method of the argument on the left
In [ ]:
B == B
In [ ]:
B == C
In [ ]:
C == D
In [ ]:
B == D
In [ ]:
# check the directory of the object's attributes (more about this later)
vars(B)
In [ ]:
vars(B) == vars(B)
In [ ]:
vars(B) == vars(C)
In [ ]:
vars(C) == vars(D)
In [ ]:
# let's cast it to a real 'dict'
dict(vars(D))
In [ ]:
dict(vars(B)) == dict(vars(C))
In [ ]:
dict(vars(C)) == dict(vars(D))
In [ ]:
# check the directory of attributes (more about this later)
dir(B)
In [ ]:
dir(B) == dir(B)
In [ ]:
dir(B) == dir(C)
In [ ]:
dir(C) == dir(D)
Takeaways:
vars
, dir
, etc.) are equivalent for self-comparison
In [ ]:
# instance and type
isinstance(B,type)
In [ ]:
isinstance(B,object)
In [ ]:
type(B)
In [ ]:
B.__class__
In [ ]:
B.__base__
In [ ]:
B.__bases__
In [ ]:
id(B)
In [ ]:
# the 'is' operator compares the result of the 'id' function's application to the arguments
B is B
In [ ]:
id(B) == id(B)
In [ ]:
# now use B's callability to create an instance of it
b = B()
In [ ]:
isinstance(b,B)
In [ ]:
type(b).__bases__
In [ ]:
# FWIW
type(type)
In [ ]:
type.__bases__
Takeaways:
WTF?
In addition to various notions of identity, we also need to investigate attribute access.
Apart from the problem we're investigating, Python places a lot of importance on interfaces, in which an object is described and classified in terms of its function and attributes, rather than its identity or inheritance properties.
In [ ]:
# set some attributes of some objects
setattr(b,'an_instance_attr',1)
setattr(B,'a_class_attr',2)
setattr(B,'a_class_method',lambda x: 3)
In [ ]:
vars(b)
In [ ]:
b.__dict__
In [ ]:
vars(B)
Conclusion: __dict__
/ vars()
returns an instance's attributes.
Let iterate through b
's inheritance tree, and look at the instance attributes.
In [ ]:
vars(type)
In [ ]:
vars(object)
In [ ]:
# collect all the instance attributes of the inheritance tree (don't include type)
attribute_keys = set( list(vars(b).keys()) + list(vars(B).keys()) + list(vars(object).keys()))
In [ ]:
for attribute_key in attribute_keys:
print('{} : {}'.format(attribute_key,getattr(b,attribute_key)))
In [ ]:
# our manual attributes collection should match that from 'dir'
attribute_keys - set(dir(b))
NOTE: dir
is not always reliable.
Take-aways:
__dict__
attribute lists the instance attributes of an object
In [ ]:
b.an_instance_attr
In [ ]:
B.an_instance_attr
In [ ]:
B.a_class_attr
In [ ]:
b.a_class_attr
In [ ]:
b.a_class_method
In [ ]:
b.a_class_method()
Take-aways:
Out of scope:
In [ ]:
B.mro()
In [ ]:
# Python's MRO invokes a smart algorithm that accounts for circularity in the inheritance tree
# https://en.wikipedia.org/wiki/C3_linearization
class X():
a = 1
class Y():
b = 2
class Z(X,Y):
c = 3
Z.mro()
In [ ]:
Z.c
In [ ]:
Z.b
In [ ]:
Z.a
In [ ]:
# get an attribute defined only by the base class
Z.__repr__
To locate the attribute named my_attr
, Python:
__dict__
attribute of the instance for key my_attr
__dict__
attributes of all the objects in the MRO__getattr__
method, and calls object.__getattr__('my_attr')
Take-aways:
In [ ]:
# let's start with the instance-level attribute dictionary
b.__dict__['an_attr'] = 'value'
b.__dict__
In [ ]:
# I don't know why anyone would want to do this, but we'll allow it at the level of instance objects.
# Any hashable object can be a key in an ordinary dictionary.
b.__dict__[1] = [3,4]
In [ ]:
# what happens if we do the same to `b`'s class?
b.__class__.__dict__[1] = [3,4]
In [ ]:
# right, we've seen this "mappingproxy" before
b.__class__.__dict__
In [ ]:
# also equivalent
B.__dict__
The MappingProxyType type is a read-only view of a mapping (dictionary). So we can't set instance attributes via this attribute. This requires that attributes be set with setattr
, which calls __setattr__
.
In [ ]:
# turns out, it's a method of 'object'
B.__setattr__
In [ ]:
setattr(B,1,2)
Take-away:
object.__setattr__
, thus speeding up attribute lookup.
In [ ]:
%%HTML
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Pay no mind.... <a href="https://t.co/mnIPHJXE1h">pic.twitter.com/mnIPHJXE1h</a></p>— David Beazley (@dabeaz) <a href="https://twitter.com/dabeaz/status/890634046958477312">July 27, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
In [ ]:
# the example
A.__dict__ is A.__dict__
In [ ]:
# run this a few times
id(A.__dict__)
Takeaway: a new mappingproxy
object is created for every call to __dict__
, and since two objects can't share the same memory address at the same time, this form of comparison will never be true. The reason that a new mappingproxy
is created for each call to __dict__
is, unfortunately, out of scope.
Bonus questions below:
In [ ]:
# what about this?
id(A.__dict__) == id(A.__dict__)
In [ ]:
# or this?
x = id(A.__dict__)
y = id(A.__dict__)
x == y
In [ ]:
# or this?
x = A.__dict__
y = A.__dict__
id(x) == id(y)
Remember: the return value of the id
builtin function "is an integer which is guaranteed to be unique and constant for this object during its lifetime."
In [ ]: