- 5.1 Function Definitions
- 5.2 Default Arguments
- 5.3 Variadic Arguments
- 5.4 Keyword Arguments
- 5.5 Variadic Keyword Arguments
- 5.6 Functions Accepting All Inputs
- 5.7 Positional-Only Arguments
- 5.8 Names, Documentation Strings, and Type Hints
- 5.9 Function Application and Parameter Passing
- 5.10 Return Values
- 5.11 Error Handling
- 5.12 Scoping Rules
- 5.13 Recursion
- 5.14 The lambda Expression
- 5.15 Higher-Order Functions
- 5.16 Argument Passing in Callback Functions
- 5.17 Returning Results from Callbacks
- 5.18 Decorators
- 5.19 Map, Filter, and Reduce
- 5.20 Function Introspection, Attributes, and Signatures
- 5.21 Environment Inspection
- 5.22 Dynamic Code Execution and Creation
- 5.23 Asynchronous Functions and await
- 5.24 Final Words: Thoughts on Functions and Composition
5.20 Function Introspection, Attributes, and Signatures
As you have seen, functions are objects—which means they can be assigned to variables, placed in data structures, and used in the same way as any other kind of data in a program. They can also be inspected in various ways. Table 5.1 shows some common attributes of functions. Many of these attributes are useful in debugging, logging, and other operations involving functions.
Table 5.1 Function Attributes
Attribute |
Description |
---|---|
f.__name__ |
Function name |
f.__qualname__ |
Fully qualified name (if nested) |
f.__module__ |
Name of module in which defined |
f.__doc__ |
Documentation string |
f.__annotations__ |
Type hints |
f.__globals__ |
Dictionary that is the global namespace |
f.__closure__ |
Closure variables (if any) |
f.__code__ |
Underlying code object |
The f.__name__ attribute contains the name that was used when defining a function. f.__qualname__ is a longer name that includes additional information about the surrounding definition environment.
The f.__module__ attribute is a string that holds the module name in which the function was defined. The f.__globals__ attribute is a dictionary that serves as the global namespace for the function. It is normally the same dictionary that’s attached to the associated module object.
f.__doc__ holds the function documentation string. The f.__annotations__ attribute is a dictionary that holds type hints, if any.
f.__closure__ holds references to the values of closure variables for nested functions. These are a bit buried, but the following example shows how to view them:
def add(x, y): def do_add(): return x + y return do_add >>> a = add(2, 3) >>> a.__closure__ (<cell at 0x10edf1e20: int object at 0x10ecc1950>, <cell at 0x10edf1d90: int object at 0x10ecc1970>) >>> a.__closure__[0].cell_contents 2 >>>
The f.__code__ object represents the compiled interpreter bytecode for the function body.
Functions can have arbitrary attributes attached to them. Here’s an example:
def func(): statements func.secure = 1 func.private = 1
Attributes are not visible within the function body—they are not local variables and do not appear as names in the execution environment. The main use of function attributes is to store extra metadata. Sometimes frameworks or various metaprogramming techniques utilize function tagging—that is, attaching attributes to functions. One example is the @abstractmethod decorator that’s used on methods within abstract base classes. All that decorator does is attach an attribute:
def abstractmethod(func): func.__isabstractmethod__ = True return func
Some other bit of code (in this case, a metaclass) looks for this attribute and uses it to add extra checks to instance creation.
If you want to know more about a function’s parameters, you can obtain its signature using the inspect.signature() function:
import inspect def func(x: int, y:float, debug=False) -> float: pass sig = inspect.signature(func)
Signature objects provide many convenient features for printing and obtaining detailed information about the parameters. For example:
# Print out the signature in a nice form print(sig) # Produces (x: int, y: float, debug=False) -> float # Get a list of argument names print(list(sig.parmeters)) # Produces [ 'x', 'y', 'debug'] # Iterate over the parameters and print various metadata for p in sig.parameters.values(): print('name', p.name) print('annotation', p.annotation) print('kind', p.kind) print('default', p.default)
A signature is metadata that describes the nature of a function—how you would call it, type hints, and so on. There are various things that you might do with a signature. One useful operation on signatures is comparison. For example, here’s how you check to see if two functions have the same signature:
def func1(x, y): pass def func2(x, y): pass assert inspect.signature(func1) == inspect.signature(func2)
This kind of comparison might be useful in frameworks. For example, a framework could use signature comparison to see if you’re writing functions or methods that conform to an expected prototype.
If stored in the __signature__ attribute of a function, a signature will be shown in help messages and returned on further uses of inspect.signature(). For example:
def func(x, y, z=None): ... func.__signature__ = inspect.signature(lambda x,y: None)
In this example, the optional argument z would be hidden in further inspection of func. Instead, the attached signature would be returned by inspect.signature().