On 11Aug2019 22:58, James Hartley <jjhart...@gmail.com> 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. (Also, I wouldn't have to import the area function explicitly - it comes along with the class nicely.)

So the static method is used to associate it with the class it supports, for use when the caller doesn't have an instance to hand.

Cheers,
Cameron Simpson <c...@cskk.id.au>
_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

Reply via email to