What's the difference between @staticmethod and @classmethod?

The simplest way to understand the difference is to consider them in different categories. The @staticmethod decorator is in that context just a function wrapped by a class body. It could have lived outside the class anyway as it doesn't refer to any of its specific properties. The @classmethod decorator is different as it lets you call the function on a class. Probably one of the most obvious use cases is the factory pattern, hence an alternative instance initialization, as presented below:

from __future__ import annotations


class Letter:

  def __init__(self, letter: str):
     self.__letter = letter

  @property
  def letter(self) -> str:
     return self.__letter

  @classmethod
  def from_int(cls, letter_int: int) -> Letter:
     return cls(chr(letter_int))

letter_string = Letter('A')
assert(letter_string.letter == 'A')
letter_int = Letter.from_int(112)
assert(letter_int.letter == 'p')

Besides, the @classmethod can be also overridden while the @staticmethod can't, since it's attached statically to a class:

class UpperCaseLetter(Letter):

  @classmethod
  def from_int(cls, letter_int: int) -> Letter:
     return cls(chr(letter_int).upper())


letter_string = UpperCaseLetter('a')
assert (letter_string.letter == 'a')
letter_int = UpperCaseLetter.from_int(112)
assert (letter_int.letter == 'P')