I have a class, one of its attributes is a class method that I want to run at program exit. This immediate idea:
import atexit
class Foo:
@atexit.register
@classmethod
def foo(cls):
pass
raises the following exception:
Traceback (most recent call last):
File "test.py", line 3, in <module>
class Foo:
File "test.py", line 5, in Foo
@classmethod
TypeError: the first argument must be callable
Another immediate idea (notice the changed evaluation order of decorators):
import atexit
class Foo:
@classmethod
@atexit.register
def foo(cls):
pass
raises the following exception:
Error in atexit._run_exitfuncs:
TypeError: foo() missing 1 required positional argument: 'cls'
I am quite new to the concept of decorators.
First you should read the answer for How does a classmethod object work?
But it would be interesting to see the output of the following code:
def mydeco(func):
print(repr(func))
return func
class Foo:
@mydeco
@classmethod
def foo(cls):
pass
print(repr(Foo.foo))
If you run this, you will see
<classmethod object at 0x6ffffd1dc18>
<bound method Foo.foo of <class '__main__.Foo'>>
So the first line is from the mydeco()
decorator and the second line is from the print()
statement at the bottom. You see that they are different. The reason they are different is because classmethod
is not like the function decorator you expect. It does not give you back a function, but a classmethod
object, and it is not callable. However, at the same time, the class Foo
that encloses it remember that in Foo.__dict__
and when you recall the decorated class method, it give you back a callable method.
Just because the classmethod
object is not callable. You cannot wrap it with atexit.register
. Similar case for staticmethod
.
So now you should realize, to register that into atexit
, you can do it outside the class, like this:
import atexit
class Foo:
@classmethod
def foo(cls):
pass
atexit.register(Foo.foo)