<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 WTF is this?
or, When is is what you think is is?
Also, rabbit hole alert...
Step1:  The Problem
Step2:  Our path...
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
Step3:  Takeaways
Step4:  Takeaways
Step5:  Takeaways
Step6:  Conclusion
Step7:  NOTE
Step8:  Take-aways
Step9:  To locate the attribute named my_attr, Python
Step10:  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__.
Step11:  Take-away
Step12:  Takeaway
<ASSISTANT_TASK:>
Python Code:
%%HTML
<img src="https://imgs.xkcd.com/comics/bun_alert.png" width=500></img>
Explanation: WTF is this?
or, When is is what you think is is?
Also, rabbit hole alert...
End of explanation
%%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>&mdash; 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>
# let's reproduce it
class A():
    pass
A.__dict__ is A.__dict__
# ... and more robustly...
a = A()
a.__class__.__dict__ is a.__class__.__dict__
Explanation: The Problem
End of explanation
class B():
    pass
C = type('C',(),dict())
D = type('C',(),dict())
D
Explanation: Our path...
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:
* we're not gonna talk about properties (by name)
* we're not gonna talk about descriptors (by name)
* we're not gonna talk about slots
...but you will run into these concepts if you investigate beyond this tutorial.
1) Python class construction
End of explanation
# Start with the equivalence operator (==)
# --> remember that this will be defined by the ".__eq__()" method of the argument on the left
B == B
B == C
C == D
B == D
# check the directory of the object's attributes (more about this later)
vars(B)
vars(B) == vars(B)
vars(B) == vars(C)
vars(C) == vars(D)
# let's cast it to a real 'dict'
dict(vars(D))
dict(vars(B)) == dict(vars(C))
dict(vars(C)) == dict(vars(D))
# check the directory of attributes (more about this later)
dir(B)
dir(B) == dir(B)
dir(B) == dir(C)
dir(C) == dir(D)
Explanation: Takeaways:
two forms of class definition
variables point to objects
Reminder:
all objects in python 3 are instances of object, including objects that are class definitions
2) Python class comparision
How do these class definitions compare?
End of explanation
# instance and type
isinstance(B,type)
isinstance(B,object)
type(B)
B.__class__
B.__base__
B.__bases__
id(B)
# the 'is' operator compares the result of the 'id' function's application to the arguments
B is B
id(B) == id(B)
# now use B's callability to create an instance of it
b = B()
isinstance(b,B)
type(b).__bases__
# FWIW
type(type)
type.__bases__
Explanation: Takeaways:
Class definitions are objects with attributes
Class descriptions (vars, dir, etc.) are equivalent for self-comparison
Only the objects' list of attribute names are equivalent for separately-constructed objects
3) Python class identity
What are these objects?
End of explanation
# 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)
vars(b)
b.__dict__
vars(B)
Explanation: Takeaways:
class objects are instances of the type 'type'
class objects are classes that inherit from 'object'
WTF?
4) Object attributes
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.
End of explanation
vars(type)
vars(object)
# 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()))
for attribute_key in attribute_keys:
    print('{} : {}'.format(attribute_key,getattr(b,attribute_key)))
# our manual attributes collection should match that from 'dir'
attribute_keys - set(dir(b))
Explanation: Conclusion: __dict__ / vars() returns an instance's attributes.
Let iterate through b's inheritance tree, and look at the instance attributes.
End of explanation
b.an_instance_attr
B.an_instance_attr
B.a_class_attr
b.a_class_attr
b.a_class_method
b.a_class_method()
Explanation: NOTE: dir is not always reliable.
Take-aways:
The __dict__ attribute lists the instance attributes of an object
5) Instance and class attributes
End of explanation
B.mro()
# 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()
Z.c
Z.b
Z.a
# get an attribute defined only by the base class
Z.__repr__
Explanation: Take-aways:
instance attributes do not affect the associated class attribute set
class attributes are available for lookup by an instance
Out of scope:
how do instance attributes get added at construction?
6) Attribute access
The dot notation searches through the attributes of the instance, then the class, the through parent classes, to find an attribute of the requested name. 
The method resolution order defines how complex inheritance structures are traversed.
End of explanation
# let's start with the instance-level attribute dictionary
b.__dict__['an_attr'] = 'value'
b.__dict__
# 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]
# what happens if we do the same to `b`'s class?
b.__class__.__dict__[1] = [3,4]
# right, we've seen this "mappingproxy" before
b.__class__.__dict__
# also equivalent
B.__dict__
Explanation: To locate the attribute named my_attr, Python:
searchs the __dict__ attribute of the instance for key my_attr
searches the __dict__ attributes of all the objects in the MRO
searches in all the places for a __getattr__ method, and calls object.__getattr__('my_attr')
...other things...
Take-aways:
the method resolution order manages the order and sources for object attribute lookup
attribute lookup is potentially complicated
7) An optimization
Because attribute lookup is common and potentially complicated, 
the Python authors decided to enforce some simplifications to the process. 
Most important for our problem here: class-level attributes and methods must by referenced with strings.
End of explanation
# turns out, it's a method of 'object'
B.__setattr__
setattr(B,1,2)
Explanation: 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__.
End of explanation
%%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>&mdash; 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>
# the example
A.__dict__ is A.__dict__
# run this a few times
id(A.__dict__)
Explanation: Take-away:
class attributes are required to be referenced by strings, due to the implementation of object.__setattr__, thus speeding up attribute lookup.
the class-level attribute mapping is returned by a read-only mappingproxy object
8) Tying it together
Now we know why an object's __dict__ attribute returns a read-only mappingproxy object. Let's return to the Tweet and address the question of the mappingproxy object's identity.
End of explanation
# what about this?
id(A.__dict__) == id(A.__dict__)
# or this?
x = id(A.__dict__)
y = id(A.__dict__)
x == y
# or this?
x = A.__dict__
y = A.__dict__
id(x) == id(y)
Explanation: 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:
End of explanation
<END_TASK>