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

Reply via email to