1

I've seen discussions about difference between @classmethod and @staticmethod and about that between @staticmethod and global functions, but I am still confused about the difference between @staticmethod and sub-function in a function.

Consider this example below (revised from The definitive guide on how to use static, class or abstract methods in Python):

class Pizza(object):
    def __init__(self, cheese, vegetables):
        self.cheese = cheese
        self.vegetables = vegetables

    @staticmethod
    def mix_ingredients(x, y):
        return x + y

    def cook(self):
        return self.mix_ingredients(self.cheese, self.vegetables)

Why not:

def cook_pizza(cheese, vegetables):
    def mix_ingredients(x, y):
        return x + y
    return mix_ingredients(cheese, vegetables)

In this example, the function I'd ever use is cook in Pizza. Let's say there's no any other functions I'd define in Pizza in the future, i.e. I do only cooking with pizza. Can't I simply define that as one single function cook_pizza? In this way mix_ingredients won't be global and won't have name conflicts with other instances either.

Is there any advantages or disadvantages written that as a @staticmethod in this case? For example, does Pizza(cheese, vegetables).cook() perform better than cook_pizza(cheese, vegetables) because the former doesn't need to define mix_ingredients every time in the process?

ytu
  • 1,822
  • 3
  • 19
  • 42
  • 2
    You create a function each time you call `cook_pizza`, depending on any performance constraints this may be an impact. – AChampion Apr 27 '18 at 03:30
  • Duplicate of https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python perhaps. – Mike S. Apr 27 '18 at 03:35
  • @MikeS. This is definitely not a duplicate of that. That thread you posted is still related to difference between `@staticmethod` and `@classmethod`. – ytu Apr 27 '18 at 03:45
  • If performance really matters here, obviously the right answer is to manually inline it: `return cheese + vegetables`. There's an extra cost for an attribute lookup and trivial descriptor call when you call a `staticmethod`; there's an extra cost for building a function object when you call a local function. Both are pretty small, but if your code is so trivial, and called so often, that either one is too slow, the other one is probably too slow as well. – abarnert Apr 27 '18 at 04:29
  • Also, I know this is something you got from someone else's tutorial, but I don't think this really is a very good use of `staticmethod`. Or for a public method of any kind. This looks like a customization hook method for subclasses, in which case it should probably be at least a `classmethod`, and probably a normal instance method. For example, a `DeepDishPizza` method might want to mix the cheese and veggies differently depending on whether it was a small or large deep dish pizza. – abarnert Apr 27 '18 at 04:35
  • (Plus, I'd question how "definitive" a tutorial on staticmethod is if it doesn't even mention using them to providing a default value for a function-typed attribute, which the stdlib does in multiple places, and which confuses many people who see it the first time…) – abarnert Apr 27 '18 at 04:36

1 Answers1

2

The basic difference is the fact that you can use a function decorated with staticmethod without instantiating the class,

but if you make a sub-function/inner-function you can not access it from outside function where it was definned.

Method

Without instantiating:

In [2]: Pizza.cook()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-4a252d4e9619> in <module>()
----> 1 Pizza.cook()

TypeError: cook() missing 1 required positional argument: 'self'

With instance:

In [4]: Pizza('cheese', 'vegetables').cook()
Out[4]: 'cheesevegetables'

Static method

Without instantiating:

In [5]: Pizza.mix_ingredients(1,2)
Out[5]: 3

Performance:

Each time you call cook_pizza it defines the nested function, which causes an impact on execution time.

import timeit

def method():
    return 'method'

def method_with_inner():
    def inner():
        return 'inner'
    return inner()

Execution

 print(timeit.timeit("method()", setup="from __main__ import method"))

0.0910306089790538

print(timeit.timeit("method_with_inner()", setup="from __main__ import method_with_inner"))

0.24090809898916632

Side note

the cook_pizza could be a static method as it doesn't use any variable defined at class level or storage in self.

Thanks: @Shadow @abarnert for the contributions in the discussion thread of this answer.

Shailyn Ortiz
  • 766
  • 4
  • 14
  • But accessing `Pizza.mix_ingredients` is not a need in this case. I do only cooking, and mixing gradients is useful only when cooking. – ytu Apr 27 '18 at 03:49
  • 1
    That's an specific case of your implementation, but most of the cases it's not like that, E.g.: Utils modules that are used from across the system then to have a lot of staticmethods because they are intended to be used a lot – Shailyn Ortiz Apr 27 '18 at 03:52
  • @ytu I've added a few more points to the answer to make it more clear, hope that helps. – Shailyn Ortiz Apr 27 '18 at 03:57
  • Well but in my real life case I wrote a function with several hundreds-of-lines sub-functions. All sub-functions are useful only for that function. I just wonder if it is necessary to use static methods. – ytu Apr 27 '18 at 03:58
  • 1
    In fact, yes, having it as static method prevents you from re-creating the sub-functions in each call, because those will be added in class instantiation (object generation), therefore improving memory consumption and speed. – Shailyn Ortiz Apr 27 '18 at 04:00
  • @ytu I've calculated the time for a method with just a sub-method and it's around 2.4x times slower. I don't want to imagine when it has more than 10 methods. – Shailyn Ortiz Apr 27 '18 at 04:06
  • This really isn't a fair performance comparison, because the first one has only one function call, and the second one has two function calls, which probably makes a lot more difference than the actual function creation. – abarnert Apr 27 '18 at 04:24
  • Also, where do you get the idea that a function "causes a lot of memory occupation"? There's only ever one at a time. And a function isn't exactly a huge thing; it's basically just a closure tuple, a default values tuple, and an annotation tuple—all empty in this case—together with a static code object. – abarnert Apr 27 '18 at 04:25
  • you are right @abarnert, but you are supposing that he just calls the function one time in their whole system, but as he has stared he has a lot of functions nested in that one, so even tho at creating they aren't really affecting "that much", at executing well... the performance is dragged to the floor. – Shailyn Ortiz Apr 27 '18 at 04:35
  • Based on the fact that even just calling the main function twice, it has too much nested code, look at this part of hes comment: <> – Shailyn Ortiz Apr 27 '18 at 04:38
  • @ShailynOrtiz I doubt it really is "dragging the performance to the floor". And if it is, using staticmethods is almost certainly _also_ going to be too slow. If he really needs to optimize that, you'd need to do something like build a tuple of plain functions and pass that in as a default parameter value. – abarnert Apr 27 '18 at 04:39
  • @ShailynOrtiz Those "hundreds-of-line" sub-functions are almost certainly going to take a whole lot more time than the function-call overhead. Unless they're just hundreds of blank lines and comments, that's a lot of code inside the body of each of those functions. – abarnert Apr 27 '18 at 04:40
  • @abarnert we don't know how many those are or the number of lines per function, we can just guess here. I'll edit my answer to correct the memory consumption part, thanks for correcting me on that, but actually, it really does affect performance in an unnecessary manner. – Shailyn Ortiz Apr 27 '18 at 04:42
  • 1
    @ShailynOrtiz You just quoted him: "several hundreds-of-lines sub-functions". So yes, we _do_ know how many there are (several), and the number of lines per function (hundreds). – abarnert Apr 27 '18 at 04:42
  • 2
    As well as the performance, the accessibility changes. With a static function you can reuse it in other parts of the code. An inner function can only ever be used by the function it is defined in. This also makes inner functions much harder to write tests for... – Shadow Apr 27 '18 at 04:44
  • I just think that this answer covers the major points of the different, yet, I haven't been upvoted and I don't think it will 3 – Shailyn Ortiz Apr 27 '18 at 04:47