0

I have following class with a function:

class A: 
    def myfn():
        print("In myfn method.")

Here, the function does not have self as argument. It also does not have @classmethod or @staticmethod as decorator. However, it works if called with class:

A.myfn()

Output:

In myfn method.

But give an error if called from any instance:

a = A()
a.myfn()

Error output:

Traceback (most recent call last):
  File "testing.py", line 16, in <module>
    a.myfn()
TypeError: myfn() takes 0 positional arguments but 1 was given

probably because self was also sent as an argument.

What kind of function will this be called? Will it be a static function? Is it advisable to use function like this in classes? What is the drawback?

Edit: This function works only when called with class and not with object/instance. My main question is what is such a function called?

Edit2: It seems from the answers that this type of function, despite being the simplest form, is not accepted as legal. However, as no serious drawback is mentioned in any of many answers, I find this can be a useful construct, especially to group my own static functions in a class that I can call as needed. I would not need to create any instance of this class. In the least, it saves me from typing @staticmethod every time and makes code look less complex. It also gets derived neatly for someone to extend my class. Although all such functions can be kept at top/global level, keeping them in class is more modular. However, I feel there should be a specific name for such a simple construct which works in this specific way and it should be recognized as legal. It may also help beginners understand why self argument is needed for usual functions in a Python class. This will only add to the simplicity of this great language.

rnso
  • 23,686
  • 25
  • 112
  • 234
  • "probably because self was also sent as an argument" - right. "Will it be a static function?" - [yep!](https://stackoverflow.com/questions/735975/static-methods-in-python) – ForceBru Feb 17 '19 at 18:01
  • Can you post a link to the docs where it is mentioned that such a function will be considered to be @staticmethod ? – rnso Feb 17 '19 at 18:03
  • [This](https://stackoverflow.com/questions/735975/static-methods-in-python) post in detail explains the static method. – Sheldore Feb 17 '19 at 18:10
  • Your linked page mentions such a function as implicit `classmethod` not `staticmethod` : see `rollcall` function on that page. – rnso Feb 17 '19 at 18:12

4 Answers4

3

The function type implements the descriptor protocol, which means when you access myfn via the class or an instance of the class, you don't get the actual function back; you get instead the result of that function's __get__ method. That is,

A.myfn == A.myfn.__get__(None, A)

Here, myfn is an instance method, though one that hasn't been defined properly to be used as such. When accessed via the class, though, the return value of __get__ is simply the function object itself, and the function can be called the same as a static method.

Access via an instance results in a different call to __get__. If a is an instance of A, then

a.myfn() == A.myfn.__get__(a, A)

Here , __get__ tries to return, essentially, a partial application of myfn to a, but because myfn doesn't take any arguments, that fails.

You might ask, what is a static method? staticmethod is a type that wraps a function and defines its own __get__ method. That method returns the underlying function whether or not the attribute is accessed via the class or an instance. Otherwise, there is very little difference between a static method and an ordinary function.

chepner
  • 497,756
  • 71
  • 530
  • 681
2

This is not a true method. Correctly declarated instance methods should have a self argument (the name is only a convention and can be changed if you want hard to read code), and classmethods and staticmethods should be introduced by their respective decorator.

But at a lower level, def in a class declaration just creates a function and assigns it to a class member. That is exactly what happens here: A.my_fn is a function and can successfully be called as A.my_fn().

But as it was not declared with @staticmethod, it is not a true static method and it cannot be applied on a A instance. Python sees a member of that name that happens to be a function which is neither a static nor a class method, so it prepends the current instance to the list of arguments and tries to execute it.

To answer your exact question, this is not a method but just a function that happens to be assigned to a class member.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
1

Such a function isn't the same as what @staticmethod provides, but is indeed a static method of sorts.

With @staticmethod you can also call the static method on an instance of the class. If A is a class and A.a is a static method, you'll be able to do both A.a() and A().a(). Without this decorator, only the first example will work, because for the second one, as you correctly noticed, "self [will] also [be] sent as an argument":

class A:
    @staticmethod
    def a():
        return 1

Running this:

>>> A.a()  # `A` is the class itself
1
>>> A().a()  # `A()` is an instance of the class `A`
1

On the other hand:

class B:
    def b():
        return 2

Now, the second version doesn't work:

>>> B.b()
2
>>> B().b()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: b() takes 0 positional arguments but 1 was given
ForceBru
  • 43,482
  • 10
  • 63
  • 98
1

further to @chepnet's answer, if you define a class whose objects implement the descriptor protocol like:

class Descr:
    def __get__(self, obj, type=None):
        print('get', obj, type)
    def __set__(self, obj, value):
        print('set', obj, value)
    def __delete__(self, obj):
        print('delete', obj)

you can embed an instance of this in a class and invoke various operations on it:

class Foo:
    foo = Descr()

Foo.foo
obj = Foo()
obj.foo

which outputs:

get None <class '__main__.Foo'>
get <__main__.Foo object at 0x106d4f9b0> <class '__main__.Foo'>

as functions also implement the descriptor protocol, we can replay this by doing:

def bar():
    pass

print(bar)
print(bar.__get__(None, Foo))
print(bar.__get__(obj, Foo))

which outputs:

<function bar at 0x1062da730>
<function bar at 0x1062da730>
<bound method bar of <__main__.Foo object at 0x106d4f9b0>>

hopefully that complements chepnet's answer which I found a little terse/opaque

Sam Mason
  • 15,216
  • 1
  • 41
  • 60