[Tutor] class functions/staticmethod?
Steven D'Aprano
steve at pearwood.info
Mon Aug 12 05:26:26 EDT 2019
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
More information about the Tutor
mailing list