Re: [Tutor] class functions/staticmethod?
On 14Aug2019 11:15, Steven D'Aprano wrote: On Wed, Aug 14, 2019 at 09:58:35AM +1000, Cameron Simpson wrote: On 11Aug2019 22:58, James Hartley wrote: >I am lacking in understanding of the @staticmethod property. >Explanation(s)/links might be helpful. I have not found the descriptions >found in the Internet wild to be particularly instructive. You have received some answers; to me they seem detailed enough to be confusing. Its only confusing if you don't work your way through it carefully and systematically. There's a lot to understand, but if you don't understand it, Python's behaviour in this case seems counter-intuitive and hard to follow. Yeah, but it helps to understand the objective: function context. A deep dive into the mechanisms used to achieve that is a load to ingest. High levels of detail tend to swamp one's view of the larger picture, particularly when learning. [...] I think of things this way: what context does a method require? Not everything needs the calling instance. Here endeth the lesson. Given that you go on to write almost another 150 lines of explanation, I think a better description would be "Here *begins* the lesson" *wink* Well, maybe, but I really wanted to highlight the objective: @classmethod and @staticmethod dictate the context provided to the method. All the examples that follow aim, however vaguely, to show those contexts in action. Your lesson, I think, assumes that it is obvious that staticmethods don't have access to the calling instance, or its class. No, it aims to make that point clear. EVerything else is example or mechanism. But if you look at James' code, I think you will agree that he's assuming that staticmethods *do* have access to the calling class, and is perplexed by the fact that the look-up of class variables (class attributes) fails. Because nobody had said that @staticmethod and @classmethod _define_ the provided context. Your lesson gives us no clue why James' first method, "dimensions()", which he describes as a "class method", isn't a class method and doesn't actually work correctly, even though it appears to at first glance. I didn't try to tackle his code. I think it is better to get the intended use of @classmethod and @staticmethod clear. Digging into whatever weird consequences there might be to his slightly wrong code just brings confusion. Cheers, Cameron Simpson ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] class functions/staticmethod?
On Wed, Aug 14, 2019 at 09:58:35AM +1000, Cameron Simpson wrote: > On 11Aug2019 22:58, James Hartley wrote: > >I am lacking in understanding of the @staticmethod property. > >Explanation(s)/links might be helpful. I have not found the descriptions > >found in the Internet wild to be particularly instructive. > > You have received some answers; to me they seem detailed enough to be > confusing. Its only confusing if you don't work your way through it carefully and systematically. There's a lot to understand, but if you don't understand it, Python's behaviour in this case seems counter-intuitive and hard to follow. Python makes the behaviour of regular instance methods so simple and intuitive, it can be quite a blow when you try to do something that isn't. > I think of things this way: what context does a method require? Not > everything needs the calling instance. > > Here endeth the lesson. Given that you go on to write almost another 150 lines of explanation, I think a better description would be "Here *begins* the lesson" *wink* Your lesson, I think, assumes that it is obvious that staticmethods don't have access to the calling instance, or its class. But if you look at James' code, I think you will agree that he's assuming that staticmethods *do* have access to the calling class, and is perplexed by the fact that the look-up of class variables (class attributes) fails. If James comes from a Java background, he's probably assuming that static methods do have access to the class variables, using undotted names: class K(object): attr = 1 @staticmethod def foo(): return attr In Java, K.foo() would return 1. Your lesson gives us no clue why James' first method, "dimensions()", which he describes as a "class method", isn't a class method and doesn't actually work correctly, even though it appears to at first glance. -- Steven ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] class functions/staticmethod?
On 11Aug2019 22:58, James Hartley wrote: I am lacking in understanding of the @staticmethod property. Explanation(s)/links might be helpful. I have not found the descriptions found in the Internet wild to be particularly instructive. You have received some answers; to me they seem detailed enough to be confusing. I think of things this way: what context does a method require? Not everything needs the calling instance. Here endeth the lesson. All this stuff below is examples based on that criterion: Here's a trite example class: class Rectangle: def __init__(self, width, height): self.width=width self.height = height Most methods do things with self, and are thus "instance methods", the default. They automatically receive the instance used to call them as the first "self" argument. def area(self): return self.width * self.height They need "self" as their context to do their work. Some methods might not need an instance as context: perhaps they return information that is just based on the class, or they are factory methods intended to return a new instance of the class. Then you might use a @classmethod decorator to have the calling instance's class as the context. @classmethod def from_str(cls, s): width, height = parse_an_XxY_string(s) return cls(width, height) And some methods do not need the class or the instance to do something useful: @staticmethod def compute_area(width, height): return width * height and so we don't give them the instance or the class as context. Now, _why_? Instance methods are obvious enough - they exist to return values without the caller needing to know about the object internals. Class methods are less obvious. Consider that Python is a duck typed language: we try to arrange that provided an object has the right methods we can use various different types of objects with the same functions. For example: def total_area(flat_things): return sum(flat_thing.area() for flat_thing in flat_things) That will work for Rectangles and also other things with .area() methods. Area, though, is an instance method. Class methods tend to come into their own with subclassing: I particularly use them for factory methods. Supposing we have Rectangles and Ellipses, both subclasses of a FlatThing: class FlatThing: def __init__(self, width, height): self.width=width self.height = height @classmethod def from_str(cls, s): width, height = parse_an_XxY_string(s) return cls(width, height) class Rectangle(FlatThing): def area(self): return self.width * self.height class Ellipse(FlatThing): def area(self): return self.width * self.height * math.PI / 4 See that from_str? It is common to all the classes because they can all be characterised by their width and height. But I require the class for context in order to make a new object of the _correct_ class. Examples: rect = Rectangle.from_str("5x9") ellipse = Ellipse.from_str("5x9") ellispe2 = ellipse.from_str("6x7") Here we make a Rectangle, and "cls" is Rectangle when you call it this way. Then we make an Ellipse, and "cls" is Ellipse when called this way. And then we make another Ellipse from the first ellipse, so "cls" is again "Ellipse" (because "ellipse" is an Ellipse). You can see that regardless of how we call the factory function, the only context passed is the relevant class. And in the last example (an Ellipse from an existing Ellipse), the class comes from the instance used to make the call. So we can write some function which DOES NOT KNOW whether it gets Ellipses or Rectangles: def bigger_things(flat_things): return [ flat_thing.from_str( "%sx%s" % (flat_thing.width*2, flat_thing.height*2)) for flat_thing in flat_things ] Here we could pass in a mix if Rectangles or Ellipses (or anything else with a suitable from_str method) and get out a new list with a matching mix of bigger things. Finally, the static method. As Peter remarked, because a static method does not have the instance or class for context, it _could_ be written as an ordinary top level function. Usually we use a static method in order to group top level functions _related_ to a specific class together. It also helps with imports elsewhere. So consider the earlier: @staticmethod def compute_area(width, height): return width * height in the Rectangle class. We _could_ just write this as a top level function outside the class: def compute_rectangular_area(width, height): return width * height Now think about using that elsewhere: from flat_things_module import Rectangle, Ellipse, compute_rectangular_area area1 = compute_rectangular_area(5, 9) area2 = Rectangle.compute_area(5, 9) area3 = Ellipse.compute_area(5, 9) I would rather use the forms of "area2" and "area3" because it is clear that I'm getting an area function from a nicely named class.
Re: [Tutor] class functions/staticmethod?
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
Re: [Tutor] class functions/staticmethod?
James Hartley wrote: > I am lacking in understanding of the @staticmethod property. > Explanation(s)/links might be helpful. I have not found the descriptions > found in the Internet wild to be particularly instructive. Given the code > below: > =8<-- > 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 That works with the class as Foo.dimensions is just a function in Python 3, but not with an instance because Python will try to pass the instance as the first argument >>> Foo.dimensions() id = 140192821560880 Dimensions(height=3, width=4) >>> Foo().dimensions() Traceback (most recent call last): File "", line 1, in TypeError: dimensions() takes 0 positional arguments but 1 was given You can turn it into a static method @staticmethod def dimensions(): print('id = {}'.format(id(Foo._dimensions))) return Foo._dimensions >>> Foo.dimensions() id = 139629779179056 Dimensions(height=3, width=4) >>> Foo().dimensions() id = 139629779179056 Dimensions(height=3, width=4) or, when you are planning for subclases, into a classmethod: $ cat staticmethod_demo.py class Foo(): _dimensions = "foo-dimensions" @classmethod def class_dimensions(cls): return cls._dimensions @staticmethod def static_dimensions(): return Foo._dimensions class Bar(Foo): _dimensions = "bar-dimensions" $ python3 -i staticmethod_demo.py >>> Foo.class_dimensions(), Foo.static_dimensions() ('foo-dimensions', 'foo-dimensions') >>> Bar.class_dimensions(), Bar.static_dimensions() ('bar-dimensions', 'foo-dimensions') > > @staticmethod > def dimensions1(): > print('id = {}'.format(id(_dimensions))) > return _dimensions > =8<-- > The class method Foo.dimensions() is capable of accessing class members, > but Foo.dimensions1() cannot. What does the @staticmethod decorator really > add? You do not really need static methods; they work like module-level functions. They are more of a means to organize your code; by writing class Foo: @staticmethod def bar(...): do stuff instead of def foo_bar(...): do stuff class Foo: pass you make the mental association between the class and the function a bit stronger. ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
[Tutor] class functions/staticmethod?
I am lacking in understanding of the @staticmethod property. Explanation(s)/links might be helpful. I have not found the descriptions found in the Internet wild to be particularly instructive. Given the code below: =8<-- 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 =8<-- The class method Foo.dimensions() is capable of accessing class members, but Foo.dimensions1() cannot. What does the @staticmethod decorator really add? Thanks! ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor