consider these two pieces of code:
class M(type):
hey = []
class S(metaclass=M):
pass
class N(metaclass=M):
pass
print(S.hey) # output []
print(N.hey) # output []
S.hey.append(6)
print(S.hey) # output [6]
print(N.hey) # output [6]
As you can see, changing hey list on S also modifies the hey that N has. So one can assume they are "sharing" this object whose reference is a class variable in their shared metaclass.
Now consider this version:
class M(type):
hey = 5
class S(metaclass=M):
pass
class N(metaclass=M):
pass
print(S.hey) # output 5
print(N.hey) # output 5
S.hey = 6
print(S.hey) # output 6
print(N.hey) # output 5
Here we see that modifying hey for S does not affect its value when accessed through H! I have to assume that primitives behave this way.
My question is: This seems weird to me, so is there some kind of documentation on these topics that I can read to learn the "rules"? I skimmed through the official Python documentation but there is very little about the OOP side of the language.
Also, why is python designed this way? What is the design deliberation behind this behavior?
EDIT: also, what do you call S and N in my code examples with respect to M? I inaccurately called them subclasses for M, but what is the correct term for them?
I was expecting consistent behavior between primitives and objects but the behavior changes
>Solution :
You don’t need classes and metaclasses to see this principle.
a = []
b = a
c = a
a.append(3)
If you now print a, b or c, you’ll see [3]. That snippet contains exactly ONE list. It just has three different names. But if I do:
b = 77
That binds a new integer object to the name b. a and c are still bound to that original list, but b now has a different lifetime.
This is also the cause of a very common error:
a, b, c = [[]] * 3
It is usually a surprise to people to learn that this snippet ALSO contains exactly one list. To initialize three different lists, you need something like:
a, b, c = ([] for _ in range(3))