Created
December 10, 2015 23:14
-
-
Save goerz/576f13e1a362bf5c6e3f to your computer and use it in GitHub Desktop.
Example code that illustrates the (possibly surprising) behavior of Python instance and class attributes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyNewClass(object): | |
a = 1 # class attribute | |
def __init__(self): | |
self.b = 1 # instance attribute | |
def get_a_via_instance(self): | |
return self.a | |
def get_a_via_class(self): | |
return self.__class__.a # = MyNewClass.a | |
class MyOldClass(): | |
# This is an old style class (Python 2 only) | |
a = 1 # class attribute | |
def __init__(self): | |
self.b = 1 # instance attribute | |
def get_a_via_instance(self): | |
return self.a | |
def get_a_via_class(self): | |
return self.__class__.a # = MyOldClass.a | |
class MySlotClass(object): | |
__slots__ = ['b', ] | |
a = 1 # class attribute | |
def __init__(self): | |
self.b = 1 # instance attribute | |
def get_a_via_instance(self): | |
return self.a | |
def get_a_via_class(self): | |
return self.__class__.a # = MySlotClass.a | |
def assert_dict(inst, attrs): | |
try: | |
assert sorted(list(inst.__dict__)) == sorted(attrs) | |
except AttributeError: | |
print(str(inst.__class__)+" has no __dict__") | |
def test_class_behavior(): | |
for cls in [MyNewClass, MyOldClass, MySlotClass]: | |
inst1 = cls() | |
inst2 = cls() | |
# all instances share the same class attribute | |
assert inst1.a == inst2.a == 1 | |
assert inst1.get_a_via_instance() == inst1.get_a_via_class() | |
cls.a = 2 | |
assert inst1.a == inst2.a == 2 | |
# the class attribute is *not* part of the instance __dict__, only the | |
# instance attribute are! | |
assert_dict(inst1, ['b', ]) | |
assert_dict(inst2, ['b', ]) | |
# we can create arbitrary new instance attributes, which are added to | |
# the instance __dict__ | |
try: | |
inst1.new_attr = 1 | |
assert_dict(inst1, ['b', 'new_attr']) | |
except AttributeError as exc: | |
# ... except if the class has __slots__ | |
print("%s: %s" % (str(cls), str(exc))) | |
# Assigning to the class attribute name of an instance does *not* set | |
# the value of the class attribute, but creates a new instance | |
# attribute that shadows the class attribute | |
try: | |
inst1.a = 1 | |
assert inst1.a == inst1.get_a_via_instance() == 1 | |
assert inst2.a == 2 | |
assert inst1.get_a_via_class() == inst2.a | |
assert_dict(inst1, ['a', 'b', 'new_attr']) | |
assert_dict(inst2, ['b', ]) | |
except AttributeError as exc: | |
# ... except if the class has __slots__ | |
print("%s: %s" % (str(cls), str(exc))) | |
# One could actually remove the shadowing instance attribute, again | |
# allowing access to the class attribute | |
try: | |
del inst1.a | |
assert_dict(inst1, ['b', 'new_attr']) | |
except AttributeError as exc: | |
print("%s: %s" % (str(cls), str(exc))) | |
assert inst1.a == 2 | |
if __name__ == "__main__": | |
test_class_behavior() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment