def this_is_a_bug(): o = object() if hasattr(o, "__call__"): print("Ooh, callable! Or is it?") if getattr(o, "__call__", False): print("Ooh, callable! Or is it?") def still_a_bug(): import builtins o = object() if builtins.hasattr(o, "__call__"): print("B U G") if builtins.getattr(o, "__call__", False): print("B U G") def trickier_fix_for_this_one(): o = object() def callable(x): return True if hasattr(o, "__call__"): print("STILL a bug!") def this_is_fine(): o = object() if callable(o): print("Ooh, this is actually callable.") # https://github.com/astral-sh/ruff/issues/18741 # The autofix for this is unsafe due to the comments. hasattr( # comment 1 obj, # comment 2 # comment 3 "__call__", # comment 4 # comment 5 ) import operator assert hasattr(operator, "__call__") assert callable(operator) is False class A: def __init__(self): self.__call__ = None assert hasattr(A(), "__call__") assert callable(A()) is False # https://github.com/astral-sh/ruff/issues/20440 def test_invalid_hasattr_calls(): hasattr(0, "__call__", 0) # 3 args - invalid hasattr(0, "__call__", x=0) # keyword arg - invalid hasattr(0, "__call__", 0, x=0) # 3 args + keyword - invalid hasattr() # no args - invalid hasattr(0) # 1 arg - invalid hasattr(*(), "__call__", "extra") # unpacking - invalid hasattr(*()) # unpacking - invalid def test_invalid_getattr_calls(): getattr(0, "__call__", None, "extra") # 4 args - invalid getattr(0, "__call__", default=None) # keyword arg - invalid getattr() # no args - invalid getattr(0) # 1 arg - invalid getattr(*(), "__call__", None, "extra") # unpacking - invalid getattr(*()) # unpacking - invalid