callable() Function Complexity¶
The callable() function checks whether an object is callable (can be invoked with arguments).
Complexity Analysis¶
| Case | Time | Space | Notes |
|---|---|---|---|
| Check callable status | O(1) | O(1) | Simple attribute check |
Object with __call__ |
O(1) | O(1) | Direct attribute lookup |
| Built-in types | O(1) | O(1) | Constant time check |
Basic Usage¶
Checking Functions and Methods¶
# O(1) - constant time check
def my_function():
pass
callable(my_function) # True
callable(len) # True
callable(str.upper) # True (bound method)
callable(str.split) # True (unbound method)
Checking Non-Callable Objects¶
# O(1) - returns False
callable(42) # False
callable("string") # False
callable([1, 2, 3]) # False
callable({"key": "value"}) # False
callable(None) # False
Classes and Instances¶
# O(1) - classes are callable
class MyClass:
pass
callable(MyClass) # True (can instantiate)
instance = MyClass()
callable(instance) # False (unless __call__ defined)
# With __call__ method
class Callable:
def __call__(self):
return "called"
obj = Callable()
callable(obj) # True (has __call__)
Complexity Details¶
How callable() Works¶
# O(1) - checks for __call__ method in type hierarchy
# Note: This is a simplified conceptual model; CPython has optimized checks
def is_callable(obj):
return hasattr(type(obj), '__call__')
# callable() is more efficient than this Python equivalent
Attribute Lookup¶
# O(1) - single attribute check
# callable() essentially checks:
# 1. obj has __call__ method
# 2. obj is a type/class
# 3. obj is a built-in function
# All constant time operations
Common Patterns¶
Function Dispatch¶
# O(1) - check before calling
def execute_if_callable(func, *args):
if callable(func):
return func(*args)
else:
return func
result = execute_if_callable(len, [1, 2, 3]) # 3
value = execute_if_callable(42) # 42
Callback Validation¶
# O(1) - validate callback parameter
def register_callback(callback):
if not callable(callback):
raise TypeError(f"callback must be callable, got {type(callback)}")
# Register callback
return callback
# Valid
register_callback(lambda x: x + 1) # OK
# Invalid
try:
register_callback(42) # TypeError
except TypeError as e:
print(e)
Polymorphic Processing¶
# O(1) - per item, handle both callables and values
def process(items):
results = []
for item in items:
if callable(item):
results.append(item()) # Call it
else:
results.append(item) # Use as-is
return results
values = [42, lambda: 100, "string", len]
# results = [42, 100, "string", <function len>]
Callables in Python¶
Functions¶
# All are callable O(1)
# Regular function
def func():
pass
callable(func) # True
# Lambda
f = lambda x: x + 1
callable(f) # True
# Built-in function
callable(len) # True
callable(print) # True
# Method
obj = "hello"
callable(obj.upper) # True
Classes¶
# O(1) - classes are callable (they instantiate)
class MyClass:
pass
callable(MyClass) # True
# Built-in types
callable(int) # True
callable(str) # True
callable(list) # True
callable(dict) # True
Objects with call¶
# O(1) - check for __call__ method
class Callable:
def __call__(self):
return "I was called"
obj = Callable()
callable(obj) # True
result = obj() # "I was called"
Performance Patterns¶
vs isinstance() Checks¶
# Both O(1), but different purposes
import types
obj = lambda x: x + 1
# callable() - high level
if callable(obj):
pass
# isinstance() - more specific
if isinstance(obj, types.FunctionType):
pass
# callable() is cleaner for just checking if callable
Batch Processing¶
# O(n) - check n items
# n calls to callable(), each O(1)
items = [42, lambda: 1, "str", len, [1, 2]]
callables = [x for x in items if callable(x)]
# O(n) total - [<lambda>, len]
Edge Cases¶
Partially Applied Functions¶
from functools import partial
# O(1) - partial objects are callable
add_five = partial(lambda x, y: x + y, 5)
callable(add_five) # True
result = add_five(10) # 15
Class Methods and Static Methods¶
# O(1) - decorators preserve callability
class Example:
@classmethod
def class_method(cls):
return "class"
@staticmethod
def static_method():
return "static"
def instance_method(self):
return "instance"
callable(Example.class_method) # True
callable(Example.static_method) # True
callable(Example.instance_method) # True
Generators and Iterators¶
# O(1) - functions are callable, but generators are not
def generator():
yield 1
callable(generator) # True (it's the function)
callable(generator()) # False (generator object not callable)
# Generator object has __iter__ and __next__, not __call__
Built-in Types¶
# O(1) - type check
class NotType:
pass
# Instances - not callable
instance = NotType()
callable(instance) # False
# Class - callable
callable(NotType) # True
Practical Examples¶
Lazy Evaluation¶
# O(1) - check if value or factory
def lazy_value(obj):
if callable(obj):
return obj() # Call factory
else:
return obj # Return value directly
# Usage
value1 = lazy_value(42) # 42
value2 = lazy_value(lambda: 42) # 42 (factory called)
# Useful for configuration
config = {
'static': 100,
'dynamic': lambda: expensive_calculation()
}
for key, val in config.items():
print(f"{key}: {lazy_value(val)}")
Function Composition¶
# O(1) - validate functions before composing
def compose(f, g):
if not (callable(f) and callable(g)):
raise TypeError("Both arguments must be callable")
return lambda x: f(g(x))
square = lambda x: x ** 2
double = lambda x: x * 2
composed = compose(square, double)
result = composed(5) # (5 * 2) ** 2 = 100
Event Handler Registration¶
# O(1) - validate event handler
class EventManager:
def __init__(self):
self.handlers = []
def register(self, handler):
if not callable(handler):
raise TypeError("Handler must be callable")
self.handlers.append(handler)
def emit(self, event):
for handler in self.handlers:
handler(event)
manager = EventManager()
manager.register(lambda e: print(f"Event: {e}"))
manager.emit("click") # Output: Event: click
Best Practices¶
✅ Do:
- Use
callable()to check before invoking unknown objects - Check in callback/handler validation
- Document which parameters should be callable
- Use in defensive programming
❌ Avoid:
- Assuming objects are callable without checking
- Using
callable()for type discrimination (useisinstance()for that) - Overcomplicating with
__call__when simple functions work - Adding unnecessary
callable()checks in performance-critical code
Related Functions¶
- hasattr() - Check for attribute existence
- isinstance() - Check object type
- type() - Get object type
- inspect.isfunction() - Detailed type checking
Version Notes¶
- Python 2.x:
callable()available as built-in - Python 3.0-3.1: Removed (controversial decision)
- Python 3.2+: Re-added as built-in
- All versions: O(1) constant time operation