1

I'm learning python class. I'm confused about why c.x prints "dog" (not lemon),even with the diagram. I can understand why a.x prints cat.

Specifically, I have problem understanding why x in the instance C() is points to str "dog " in global frame. And what's the self in the dash-lined box mean and why its parent frame is global frame.

enter image description here

x = "dog"


class A:
    x = "cat"


class B(A):
    x = "ferret"

    def __init__(self):
        self.x = x


class C(B):  # added
    x = "lemon"


c = C()
a = A()


print(f"{c.x = }")  # "dog"
print(f"{a.x = }")  # "cat"
rv.kvetch
  • 9,940
  • 3
  • 24
  • 53
somniumm
  • 115
  • 1
  • 10
  • https://stackoverflow.com/questions/28119489/python-class-variable-accessible-from-class-method – Thierry Lathuille Nov 01 '21 at 13:03
  • It's subtle but I guess the cause is the line `self.x = x`; since x is not defined as a param to the constructor, i'd assume it assigns the value of the global var here. Side note, you should be able to fix that by declaring it like `self.x = B.x` instead. – rv.kvetch Nov 01 '21 at 13:06
  • 2
    Because, `c` is an instance of `B` (since `C` inherits from `B`). Moreover, it inherits `B`'s `__init__`, in which you *specifically assign the global `x` to the instance attribute `x`* – juanpa.arrivillaga Nov 01 '21 at 13:07
  • Stack Overflow is not a discussion forum or tutorial resource. You should take questions like this to your instructor or TA. – Karl Knechtel Nov 01 '21 at 13:07

1 Answers1

2

Because B's constructor initializes the instance field x to the value of the global x ("dog"), not the class field x.

If you wanted it to initialize an instance field with the class field, you'd do

    def __init__(self):
        self.x = self.__class__.x

(and in fact, the non-sensical-looking

    def __init__(self):
        self.x = self.x

would do the same thing.)


As a related aside, it's good to understand the relation between instance fields and class fields:

class A:
    kind = "cat"


class B(A):
    kind = "ferret"

    def __init__(self):
        self.kind = self.kind


a1 = A()
a2 = A()
print(f"{a1.kind = } {a2.kind = }")
A.kind = "big cat"  # changes the class field, so all As are big from now on:
print(f"{a1.kind = } {a2.kind = }")
a1.kind = "medium cat"  # assigns a new instance field just to a1
print(f"{a1.kind = } {a2.kind = }")

b1 = B()
b2 = B()
print(f"{b1.kind = } {b2.kind = }")
# this has no effect on existing Bs since they copied the value from the class field:
B.kind = "hamster"  
print(f"{b1.kind = } {b2.kind = }")
# However new bs will copy that new hamster value:
b3 = B()
print(f"{b1.kind = } {b2.kind = } {b3.kind = }")

This prints out

a1.kind = 'cat' a2.kind = 'cat'
a1.kind = 'big cat' a2.kind = 'big cat'
a1.kind = 'medium cat' a2.kind = 'big cat'
b1.kind = 'ferret' b2.kind = 'ferret'
b1.kind = 'ferret' b2.kind = 'ferret'
b1.kind = 'ferret' b2.kind = 'ferret' b3.kind = 'hamster'
AKX
  • 152,115
  • 15
  • 115
  • 172
  • Why does the __init__ function creates an enclosing frame to the global frame? would regular function also have the same impact? – somniumm Nov 01 '21 at 13:00
  • What do you mean with "creates an enclosing frame to the global frame"? – AKX Nov 01 '21 at 13:01
  • in the digram, there's a blue line with "enclosing frame" pointing to global frame. what does it mean? – somniumm Nov 01 '21 at 13:02
  • 1
    `__init__` is a function; functions create new scopes when executed. The only role the scope plays here, though, is in providing a namespace where the name `self` is defined, since you never use a local variable named `x`. All functions retain a reference to the scope in which the function was defined. `__init__` was defined in the global scope, so it keeps a reference to the global scope. – chepner Nov 01 '21 at 13:05
  • 1
    "in the digram, there's a blue line with "enclosing frame" pointing to global frame. what does it mean?" Well, who drew the diagram? Did you try asking that person what it means? – Karl Knechtel Nov 01 '21 at 13:08
  • 2
    @somniumm it *doesn't*. There is nothing special about `__init__` here. This is just the typical Python scoping rules. Perhaps, importantly, you must understand that *class blocks* **don't** create enclosing scopes – juanpa.arrivillaga Nov 01 '21 at 13:08
  • @juanpa.arrivillaga i found your explanation most clear to me. My misconception is that class creates an enclosing scope.. – somniumm Nov 01 '21 at 13:16
  • 1
    @somniumm yes, I thought so. Note, this is why you must refer to *other methods* using `self` (or unconventionally, using the class directly). – juanpa.arrivillaga Nov 01 '21 at 13:17