from inspect import isgeneratorfunction def f(a, b): return a + b assert not isgeneratorfunction(f) def g(a, b): yield a yield b assert isgeneratorfunction(g) class A: @staticmethod def non_gen(a, b): return a + b @staticmethod def gen(a, b): yield a yield b @classmethod def non_gen_class(cls, a, b): return a + b @classmethod def gen_class(cls, a, b): yield a yield b def not_gen_instance(self, a, b): return a + b def gen_instance(self, a, b): yield a yield b a = A() assert not isgeneratorfunction(a.non_gen) assert isgeneratorfunction(a.gen) assert not isgeneratorfunction(A.non_gen) assert isgeneratorfunction(A.gen) assert not isgeneratorfunction(a.non_gen_class) assert isgeneratorfunction(a.gen_class) assert not isgeneratorfunction(A.non_gen_class) assert isgeneratorfunction(A.gen_class) assert not isgeneratorfunction(a.not_gen_instance) assert isgeneratorfunction(a.gen_instance) assert not isgeneratorfunction(A.not_gen_instance) assert isgeneratorfunction(A.gen_instance) # ---------------- inspect.signature ---------------- from inspect import signature, Parameter # simple positional-or-keyword parameters def f1(a, b): return a + b sig = signature(f1) assert str(sig) == '(a, b)' params = sig.parameters assert list(params) == ['a', 'b'] assert params['a'].name == 'a' assert params['a'].kind == Parameter.POSITIONAL_OR_KEYWORD assert params['a'].default is Parameter.empty assert params['b'].kind == Parameter.POSITIONAL_OR_KEYWORD # no parameters def f2(): pass sig = signature(f2) assert str(sig) == '()' assert len(sig.parameters) == 0 # default values def f3(a, b=2, c='x'): pass sig = signature(f3) assert str(sig) == "(a, b=2, c='x')" params = sig.parameters assert params['a'].default is Parameter.empty assert params['b'].default == 2 assert params['c'].default == 'x' # *args and **kwargs def f4(a, *args, **kwargs): pass sig = signature(f4) assert str(sig) == '(a, *args, **kwargs)' params = sig.parameters assert list(params) == ['a', 'args', 'kwargs'] assert params['a'].kind == Parameter.POSITIONAL_OR_KEYWORD assert params['args'].kind == Parameter.VAR_POSITIONAL assert params['args'].default is Parameter.empty assert params['kwargs'].kind == Parameter.VAR_KEYWORD assert params['kwargs'].default is Parameter.empty # keyword-only parameters (defaults after *args) def f5(a, *c, b=1, **d): pass sig = signature(f5) assert str(sig) == '(a, *c, b=1, **d)' assert list(sig.parameters) == ['a', 'c', 'b', 'd'] assert sig.parameters['b'].kind == Parameter.KEYWORD_ONLY assert sig.parameters['b'].default == 1 # lambda sig = signature(lambda x, y=3: x + y) assert str(sig) == '(x, y=3)' assert list(sig.parameters) == ['x', 'y'] assert sig.parameters['y'].default == 3 # generator function sig = signature(g) assert str(sig) == '(a, b)' # methods: bound methods drop `self`, unbound functions keep it class B: def m(self, x, y=1): pass @staticmethod def sm(x, y): pass @classmethod def cm(cls, x): pass b = B() assert str(signature(B.m)) == '(self, x, y=1)' assert str(signature(b.m)) == '(x, y=1)' assert list(signature(b.m).parameters) == ['x', 'y'] assert str(signature(B.sm)) == '(x, y)' assert str(signature(b.sm)) == '(x, y)' assert str(signature(B.cm)) == '(x)' assert str(signature(b.cm)) == '(x)' # calling signature() on a class inspects __init__ (without `self`) class C: def __init__(self, a, b=5): pass sig = signature(C) assert str(sig) == '(a, b=5)' assert list(sig.parameters) == ['a', 'b'] # decorated function (plain wrapper) def deco(fn): def wrapper(*args, **kwargs): return fn(*args, **kwargs) return wrapper @deco def f6(a, b): pass assert str(signature(f6)) == '(*args, **kwargs)' # Parameter equality / repr basics p = signature(f3).parameters['b'] assert p.name == 'b' assert p.default == 2 assert p.kind == Parameter.POSITIONAL_OR_KEYWORD # kinds are distinct kinds = [ Parameter.POSITIONAL_OR_KEYWORD, Parameter.VAR_POSITIONAL, Parameter.KEYWORD_ONLY, Parameter.VAR_KEYWORD, ] assert len(set(kinds)) == 4 # non-callable raises TypeError try: signature(42) print('failed to raise TypeError') exit(1) except TypeError: pass # ---------------- __annotations__ ---------------- from inspect import Signature def h1(a: int, *args: int, b: str = 'x', c: float = 1.5, **kwargs: str) -> bool: pass assert h1.__annotations__ == { 'a': 'int', 'args': 'int', 'b': 'str', 'c': 'float', 'kwargs': 'str', 'return': 'bool', } sig = signature(h1) assert sig.parameters['a'].annotation == 'int' assert sig.return_annotation == 'bool' assert str(sig) == "(a: int, *args: int, b: str = 'x', c: float = 1.5, **kwargs: str) -> bool" def h2(a, b: int, c=1): pass assert h2.__annotations__ == {'b': 'int'} assert str(signature(h2)) == '(a, b: int, c=1)' # complex annotation expressions are preserved as written def h3(p: list[int], q: dict[str, int]) -> 'A | None': pass assert h3.__annotations__ == {'p': 'list[int]', 'q': 'dict[str, int]', 'return': "'A | None'"} class D: def m(self, x: int) -> str: return str(x) sig = signature(D().m) assert sig.parameters['x'].annotation == 'int' assert sig.return_annotation == 'str' assert (lambda x: x).__annotations__ == {} sig = signature(lambda x: x) assert sig.parameters['x'].annotation is Parameter.empty assert sig.return_annotation is Signature.empty