Part 3.
On Sun, Aug 11, 2019 at 10:58:37PM -0500, James Hartley wrote: > from collections import namedtuple > > class Foo(): > Dimensions = namedtuple('Dimensions', ['height', 'width']) > _dimensions = Dimensions(3, 4) > > def dimensions(): > print('id = {}'.format(id(Foo._dimensions))) > return Foo._dimensions > > @staticmethod > def dimensions1(): > print('id = {}'.format(id(_dimensions))) > return _dimensions In part 2, I explained that we can re-write the dimensions() method to work correctly using the @classmethod decorator: @classmethod def dimensions(cls): print('id = {}'.format(id(cls._dimensions))) return cls._dimensions Another benefit of doing this is that it will now work correctly in subclasses. class Bar(Foo): # inherit from Foo _dimensions = (3, 4, 5, 6) # Override the parent's "dimensions". Using your definition, Bar.dimensions() will return Foo._dimensions instead of Bar._dimensions. But using the classmethod version works as expected. So why doesn't the staticmethod version work correctly? Its all to do with the way variable names are resolved by the interpreter. If you are used to Java, for example, you might expect that "class variables" (what Python calls "class attributes") are part of the scope for methods: spam = 999 # Global variable spam. class MyClass(object): spam = 1 # Class attribute ("variable") spam. def method(self): return spam instance = MyClass() If you are used to Java's rules, you would expect that instance.method() will return 1, but in Python it returns the global spam, 999. To simplify a little, the scoping rules for Python are described by the LEGB rule: - Local variables have highest priority; - followed by variables in the Enclosing function scope (if any); - followed by Global variables; - and lastly Builtins (like `len()`, `zip()`, etc). Notice that the surrounding class isn't included.[1] To access either instance attributes or class attributes, you have to explicitly say so: def method(self): return self.spam This is deliberate, and a FAQ: https://docs.python.org/3/faq/design.html#why-must-self-be-used-explicitly-in-method-definitions-and-calls Using Java's scoping rules, the staticmethod would have worked: @staticmethod def dimensions1(): print('id = {}'.format(id(_dimensions))) return _dimensions because it would see the _dimensions variable in the class scope. But Python doesn't work that way. You would have to grab hold of the class from the global scope, then grab dimensions: @staticmethod def dimensions1(): _dimensions = Foo._dimensions print('id = {}'.format(id(_dimensions))) return _dimensions If you are coming from a Java background, you may have been fooled by an unfortunate clash in terminology. A "static method" in Java is closer to a *classmethod* in Python, not a staticmethod. The main difference being that in Java, class variables (attributes) are automatically in scope; in Python you have to access them through the "cls" parameter. -- Steven _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor