Python类变量、实例属性访问机制及命名空间相关技术疑问
Great question—let’s break this down clearly, starting with your code (fixed for proper indentation) and then diving into Python’s core attribute resolution rules, plus the connection to nested function scopes.
First, Let’s Fix & Run Your Code
Here’s your code formatted correctly, with minor tweaks to avoid overwriting instances:
class Dog(object): population = 0 # Class variable: shared across all Dog instances def __init__(self, name): self.name = name self.num_legs = 4 # Instance attribute: created for every new Dog Dog.population += 1 # Increment class-level population count def get_num_legs(self): return self.num_legs # First batch of instances dog1 = Dog('dog1') dog2 = Dog('dog2') print(Dog.population) # Output: 2 print(dog1.population) # Output: 2 (not 4—you might have typoed your initial note!) # Modify dog1's instance-specific leg count dog1.num_legs = 2 print("Dog1 has {} legs".format(dog1.get_num_legs())) # Output: 2 print("Dog2 has {} legs".format(dog2.get_num_legs())) # Output: 4 # Update the Dog class's num_legs variable Dog.num_legs = 10 # Create new instances (renamed to avoid overwriting earlier ones) dog3 = Dog('dog3') dog4 = Dog('dog4') print("Dog3 has {} legs".format(dog3.get_num_legs())) # Output: 4 (your core question!) print("Dog4 has {} legs".format(dog4.get_num_legs())) # Output: 4
The Core Mechanism: Instance Attribute Resolution
Python follows a strict, scope-first rule for looking up attributes on instances—this is formally tied to the instance and class __dict__ dictionaries:
- Check the instance’s own
__dict__first: Every object in Python has a private__dict__that stores attributes set directly on it (likeself.nameorself.num_legsin your__init__method). If the attribute exists here, Python returns it immediately—it never checks the class or parent classes for this attribute. - Only if not found, check the class’s
__dict__: If the instance doesn’t have the attribute in its own dictionary, Python looks up the class hierarchy (starting with the instance’s class, then parent classes via MRO) to find a class-level variable.
In your code, the __init__ method explicitly sets self.num_legs = 4 for every new Dog instance. That means every Dog will have num_legs in its own __dict__—so when you create dog3 after updating Dog.num_legs =10, Python still grabs the instance’s num_legs=4 and never looks at the class’s value.
What If We Removed self.num_legs =4?
If you deleted that line from __init__:
- New instances wouldn’t have
num_legsin their__dict__ - Calling
dog.get_num_legs()would return the class’snum_legsvalue (10, after your update) - Any existing instances would also start returning 10, unless you’d already set an instance-specific
num_legsfor them.
Why dog1.population Returns 2
For the population variable:
- You never set
self.populationin__init__, so noDoginstance haspopulationin its__dict__ - When you call
dog1.population, Python checks the instance’s dictionary (no match), then falls back to the class’s__dict__wherepopulation=2lives. - If you later set
dog1.population =5, that would addpopulationtodog1’s__dict__, anddog1.populationwould return 5 whileDog.populationanddog2.populationstayed at 2.
Is This Similar to Nested Function Scopes?
Absolutely! This follows the same "closest scope first" logic as Python’s LEGB rule for nested functions:
- For functions: Python checks Local scope (current function variables), then Enclosing function scopes, then Global scope, then Built-in names.
- For instance attributes: Python checks the Instance’s local scope (
__dict__), then the Class’s scope (__dict__), then parent Class scopes (via MRO).
In both cases, a name in a closer scope shadows the same name in a farther scope. Python stops searching as soon as it finds a match—this is the core parallel between the two systems.
Quick Recap of Your Outputs
To tie everything together:
print(Dog.population)→ 2: Two instances created, so the class-level counter was incremented twice.print(dog1.population)→ 2:dog1has no instance-specificpopulation, so it uses the class variable.dog1.get_num_legs()→ 2:dog1’s__dict__now hasnum_legs=2, overriding any class-level value.dog2.get_num_legs()→4:dog2’s__dict__still has the originalnum_legs=4set in__init__.- New instances after
Dog.num_legs=10still return 4: Their__init__setsself.num_legs=4, so Python never checks the class’s updated value.
内容的提问来源于stack exchange,提问作者Prashin Jeevaganth

