Static Methods

A @staticmethod is a plain function that happens to live inside a class's namespace - it receives no implicit self or cls, making it the right tool when a piece of logic is related to a class conceptually but doesn't need any of its state.

Static methods are the third member of Python's method trio, alongside instance methods and class methods. Decorating a method with @staticmethod tells Python not to pass any implicit first argument - no self, no cls - so the method behaves exactly like a regular function, just namespaced inside the class for organizational purposes.

@staticmethod: No Implicit Argument

A static method is defined with the @staticmethod decorator and takes no automatic first argument. It can be called on the class or on an instance, and behaves identically either way, since it never receives self or cls.

Unlike an instance method, calling celsius_to_fahrenheit through tc does not implicitly pass tc as an argument - the method takes exactly the arguments it was defined with.

class TemperatureConverter:
    @staticmethod
    def celsius_to_fahrenheit(c: float) -> float:
        return c * 9 / 5 + 32

print(TemperatureConverter.celsius_to_fahrenheit(100))   # 212.0 - called on the class

tc = TemperatureConverter()
print(tc.celsius_to_fahrenheit(0))                          # 32.0 - called on an instance, same result

Contrast with Instance and Class Methods

The signature difference is the clearest way to see all three side by side: an instance method's first parameter is bound to the instance; a class method's first parameter is bound to the class; a static method has no such automatically-bound parameter at all.

class Demo:
    def instance_method(self):
        return f'called on instance {self}'

    @classmethod
    def class_method(cls):
        return f'called on class {cls}'

    @staticmethod
    def static_method():
        return 'called with no implicit argument'

d = Demo()
print(d.instance_method())   # called on instance <...Demo object...>
print(d.class_method())       # called on class <class '...Demo'>
print(d.static_method())       # called with no implicit argument

Why Put a Function Inside a Class at All?

A static method is used when a piece of logic is conceptually related to the class - belongs in its namespace for discoverability and organization - but doesn't need to read or write any instance or class state to do its job.

is_valid_email doesn't touch any Validator instance state - it's grouped inside Validator purely because it's thematically related validation logic.

class Validator:
    @staticmethod
    def is_valid_email(address: str) -> bool:
        return '@' in address and '.' in address.split('@')[-1]

print(Validator.is_valid_email('user@example.com'))   # True

Use Cases

Common, legitimate uses: utility/helper functions tightly scoped to a class's domain (parsing, formatting, validating inputs related to that class); grouping related pure functions under one namespace for discoverability; and providing a function that a class needs to expose as part of its public API without depending on any particular instance.

area_from_radius is useful both as part of Circle instances' behavior and standalone (e.g. computing an area without constructing a Circle at all).

class Circle:
    def __init__(self, radius: float) -> None:
        self.radius = radius

    def area(self) -> float:
        return Circle.area_from_radius(self.radius)

    @staticmethod
    def area_from_radius(radius: float) -> float:
        import math
        return math.pi * radius ** 2

print(Circle.area_from_radius(2))   # 12.566...
print(Circle(2).area())               # 12.566... - the instance method delegates to the static one

When a Module-Level Function Is Clearer

If a function doesn't need any state from the class and isn't conceptually tied to it in a way that benefits from the namespace, a plain module-level function is usually simpler and more Pythonic than forcing it into a class as a static method.

Wrapping unrelated utility functions in a class 'just to group them' is a common anti-pattern carried over from languages that require everything to live in a class - Python doesn't have that constraint.

A good rule of thumb: reach for @staticmethod when the function is meaningfully coupled to the class's domain or often called alongside the class's other methods; reach for a module-level function when it's a general-purpose utility that would be equally at home used by unrelated code.

# Prefer this for a genuinely standalone utility:
def add(a: int, b: int) -> int:
    return a + b

# Over this, which adds no value by living inside a class:
class MathUtils:
    @staticmethod
    def add(a: int, b: int) -> int:
        return a + b

Static Methods and Inheritance

Static methods are inherited like any other class member, and can be overridden in a subclass. Because they take no cls, though, an overridden static method does not automatically know which subclass called it - if that matters, a @classmethod is the right tool instead.

class Shape:
    @staticmethod
    def describe() -> str:
        return 'a generic shape'

class Square(Shape):
    @staticmethod
    def describe() -> str:
        return 'a square'

print(Shape.describe())    # a generic shape
print(Square.describe())    # a square - normal method overriding, no cls involved

Calling a Static Method from Instance and Class Methods

Instance and class methods commonly delegate to a static method for a piece of logic that doesn't need self/cls - called either via the class name or via self/cls, both work correctly.

class Circle:
    def __init__(self, radius: float) -> None:
        self.radius = radius

    def area(self) -> float:
        return self.area_from_radius(self.radius)   # called via self - works fine

    @staticmethod
    def area_from_radius(radius: float) -> float:
        import math
        return math.pi * radius ** 2

Edge Cases

A static method accessed directly from the class (not called) is just a plain function object - unlike instance methods, there is no bound-method wrapping involved, since there's no instance to bind.

Since Python 3.10, staticmethod objects are directly callable even without being accessed through a class/instance first (e.g. calling the descriptor object itself) - a minor implementation nicety that rarely matters in everyday code.

print(TemperatureConverter.celsius_to_fahrenheit)
# <function TemperatureConverter.celsius_to_fahrenheit at 0x...> - a plain function, not a bound method

Key Takeaways

@staticmethod defines a method with no implicit self or cls argument - it behaves exactly like a plain function, just namespaced under the class.

Use it for logic that is conceptually related to the class but does not need any instance or class state.

It can be called equally well via the class or an instance, with identical behavior either way.

If a function isn't meaningfully tied to the class's domain, a plain module-level function is usually clearer than forcing it into the class as a static method.

Static methods are inherited and can be overridden, but (having no cls) don't automatically know which subclass invoked them - use @classmethod if that matters.

class Validator: @staticmethod def is_valid(self, value): # 'self' here is just a normal parameter, misleadingly named return value > 0 class Validator: @staticmethod def is_valid(value): # no implicit first argument at all return value > 0

A staticmethod receives no implicit self or cls; naming a parameter self is misleading and will consume the first real argument, causing a signature mismatch at call sites.

class MathUtils: @staticmethod def add(a, b): return a + b @staticmethod def multiply(a, b): return a * b # used only as MathUtils.add(x, y) - no class ever instantiated, no shared domain benefit def add(a: int, b: int) -> int: return a + b def multiply(a: int, b: int) -> int: return a * b

Grouping unrelated general-purpose functions into a class purely for organization adds indirection without benefit in Python; module-level functions (or a module namespace) are simpler.

class Circle: @staticmethod def scaled_area(radius, factor): return Circle.area_from_radius(radius) * factor # hardcodes Circle - breaks if a subclass overrides area_from_radius class Circle: @classmethod def scaled_area(cls, radius, factor): return cls.area_from_radius(radius) * factor

If the logic needs to respect a subclass's overrides, a staticmethod (which has no cls) is the wrong tool - use @classmethod instead so cls resolves to the actual calling class.

What implicit argument does a @staticmethod receive, if any?

None - unlike instance methods (self) or class methods (cls), a static method receives no automatically-bound first argument; it behaves exactly like an ordinary function.

When is a @staticmethod the right choice over a module-level function?

When the logic is meaningfully related to the class's domain and benefits from living in its namespace (for discoverability or because it's often used alongside the class's other methods) - even though it needs no instance or class state.

Can a static method be called both via the class and via an instance? Does it behave differently either way?

Yes to both - it can be called through either, and behaves identically both ways, since no implicit self or cls is ever passed regardless of how it's accessed.

What's the difference in how @staticmethod and @classmethod handle subclassing?

@classmethod receives cls bound to whichever class it was actually called through, so it naturally adapts to subclasses; @staticmethod receives no such reference, so if overridden it behaves as ordinary method overriding without any automatic subclass-awareness.

Why might grouping several unrelated utility functions into a class as static methods be considered an anti-pattern in Python?

Because Python doesn't require code to live inside a class; wrapping general-purpose, unrelated functions in a class purely 'to organize them' adds indirection without benefit - a plain module of functions is usually clearer.

Interview: if you access ClassName.static_method without calling it, what do you get back?

The plain underlying function object itself - unlike an instance method accessed via an instance, there is no bound-method wrapping, since a static method has no instance (or class) to bind to.

Give an example of when a static method would delegate work from an instance method.

A class like Circle might implement area() as an instance method that calls a @staticmethod area_from_radius(radius) internally, so the same pure calculation logic can be reused directly (e.g. Circle.area_from_radius(5)) without needing to construct a Circle instance.

Write a class StringUtils with a static method is_palindrome(text) that returns True if the text reads the same forwards and backwards (ignoring case and spaces).

class StringUtils: @staticmethod def is_palindrome(text: str) -> bool: cleaned = text.lower().replace(' ', '') return cleaned == cleaned[::-1] print(StringUtils.is_palindrome('Race car')) # True print(StringUtils.is_palindrome('hello')) # False is_palindrome needs no instance or class state, so it's implemented as a staticmethod, callable directly as StringUtils.is_palindrome(...) without creating a StringUtils instance.

What does this print?

hello from static hello from static True Both Tool.greet() and t.greet() call the same underlying function with no implicit argument, producing identical results; since staticmethods aren't bound per-instance, Tool.greet and t.greet refer to the same underlying function object, so the equality check is True.

Write a class Rectangle with an instance method area() and a static method is_square(width, height) that area() does NOT need to use, demonstrating a staticmethod used standalone.

class Rectangle: def __init__(self, width: float, height: float) -> None: self.width = width self.height = height def area(self) -> float: return self.width * self.height @staticmethod def is_square(width: float, height: float) -> bool: return width == height r = Rectangle(4, 4) print(r.area()) # 16 print(Rectangle.is_square(4, 4)) # True - used standalone, no Rectangle needed print(Rectangle.is_square(r.width, r.height)) # True - also usable with an existing instance's data is_square is a pure, self-contained check with no dependency on any particular Rectangle instance, so it works equally well called standalone (Rectangle.is_square(4, 4)) or fed an instance's own attributes.

class Tool:
    @staticmethod
    def greet():
        return 'hello from static'

t = Tool()
print(Tool.greet())
print(t.greet())
print(Tool.greet == t.greet)