from typing import TypeVar, Any, Generic, cast, Optional, Type import functools T = TypeVar("T") class class_method_variant(Generic[T]): def __init__(self, class_method_name): self.class_method_name = class_method_name method: Any def __call__(self, method: T) -> T: self.method = method return cast(T, self) def __get__(self, obj, objtype: Optional[Type[Any]] = None): @functools.wraps(self.method) def _wrapper(*args, **kwargs): if obj is not None: # Method was called as an instance method, e.g. # instance.method(...) return self.method(obj, *args, **kwargs) elif len(args) > 0 and objtype is not None and isinstance(args[0], objtype): # Method was called as a class method with the instance as the # first argument, e.g. Class.method(instance, ...) which in # Python is the same thing as calling an instance method return self.method(args[0], *args[1:], **kwargs) else: # Method was called as a class method, e.g. Class.method(...) class_method = getattr(objtype, self.class_method_name) return class_method(*args, **kwargs) return _wrapper