Skip to content

Instantly share code, notes, and snippets.

@wd5gnr
Created November 7, 2023 22:47
Show Gist options
  • Save wd5gnr/daa5d2d344319b6ef17206f537f15b08 to your computer and use it in GitHub Desktop.
Save wd5gnr/daa5d2d344319b6ef17206f537f15b08 to your computer and use it in GitHub Desktop.
Issue with Python Class Variables
# This is what's wrong with class variables in Python
# and yes, properties work (don't work) the same way
# This is a stupid but clean example where class A
# has a class variable x. It also has direct subclasses
# B and C
# Then there is a subclass of C named D
# None of them should have an x variable, but they all will get one
class A:
x=0 # our class variable
@classmethod
def printx(cls,msg):
print(msg,cls.x)
class B(A):
@classmethod
def testit(cls):
cls.x=1
cls.printx('From cls') # prints 1
A.printx('From B') # prints 100
super().printx('From Super') # prints 1
A.x=100
A.printx('Case A') # prints 100
B.testit()
A.printx('Case A1') # prints 100
# So, ok you say, just make sure to call the base class... hmmm...
class C(A):
@classmethod
def testit(cls):
A.x=2
cls.printx("From Ccls") # prints 3
A.printx('From A') # prints 2
super().printx('From Super') # prints 3 (?)
C.x=3
A.x=200
A.printx('Case A') # prints 200
C.printx('Case C') # prints 3
C.testit()
A.printx('Case A1') # print 2
C.printx('case C1') # prints 3
# Ok so super() is broken for class variables. S'ok, right? But now you
# must know where something lives which violates encapsulation because...
class D(C):
@classmethod
def testit(cls):
C.x=2 # why not? It is our baseclass -- how do I know this is in A? Why do I even know about A?
cls.printx("From Dcls")
C.printx("from C")
super().printx('From Super')
A.printx("from A")
A.x=999
B.x=500
C.x=133
D.x=2000
A.printx('Case A') # prints 999
D.testit()
A.printx('A final') # 999
B.printx('B final') # 500
C.printx('C final') # 2
D.printx('D final') # 2000
# so from class D that derives from class C I must "know" that class variable x really comes from A.
# and I better hope that no future change puts in C or in a new base class for A.
# Here's an oddity:
class E(A):
@classmethod
def testit(cls):
print(cls.x,A.x) # 1234 1234 so right now cls.x is A.x
cls.x=3 # this creates E.x = 3
print(cls.x,A.x) # 3 1234 so now cls.x != A.x
A.x=1234
E.testit()
# Why is this bad behavior?
# Well, of cousre, someone will say it is not and that all other OO languages are wrong
# However, taking the opposite side, I would say that class variables are things
# that belong to the class as a whole. For example, say we have a bunch of People.
# Some are Writers and some are Engineers and some are Lawyers
# After I create a bunch of people I'd like to know how many of them there are.
class People:
count=0 # no people yet
@classmethod
def get_population(cls):
return cls.count
def __init__(self):
People.count+=1 # class variable
al=People()
mike=People()
lisa=People()
print("We have",People.get_population(),"generic people")
class Writer(People):
def __init__(self):
super().__init__()
self.job="Writer"
def write():
print("I'm blocked")
class Engineer(People):
def __init__(self):
super().__init__()
self.job="Engineer"
def design():
print("I'm busy")
class Lawyer(People):
def __init__(self):
super().__init__()
self.job="Lawyer"
def litigate():
print("Please deposit $800")
# So...
julie=Engineer()
mary=Lawyer()
bob=Writer()
steve=Engineer()
print("We have",People.get_population()," people")
# This works because super().__init__() calls into People and it expressly uses People.count
# But suppose we have (yes, contrived):
class JoinedTwins(People):
def __init__(self):
super().__init__()
self.job="Twins"
# self.count+=1 # nope Now we have carl_n_earl.count=1, People.count=8
# self.__class__.count+=1 # nope Now we have JoinedTwins.count=1, People.count=8
# just for fun and assume we don't have multiple inheritence.. ugly
# for xcont in self.__class__.__bases__:
# if hasattr(xcont,'count'):
# xcont.count+=1
# or does JointedTwins.count exist?
# It does. So even though we know it is there and for now
# reading from it gets People count, the moment we assign it, it belongs to us
print("Has attribute count?",hasattr(self,'count'))
People.count+=1 # Ok but as mentioned before, I have to know where it is (trivial here but not always)
def appear_on_talk_show():
print("Good evening")
carl_n_earl=JoinedTwins()
print("We have",People.get_population()," people ???")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment