runtime
169 / 250
lesson 169 Functional & Dynamic Python · advanced

Introspection: examine objects while the program runs

Python can answer questions about its own objects: is this callable, does it have this attribute, what parameters does this function take? Tools such as test runners, plugin systems and serialisers are built on those answers.

You'll be able to
  • Use callable, hasattr, getattr with a default, and setattr
  • Read a function's parameters, kinds and defaults with inspect.signature
  • Call a plugin function with only the arguments it declares
The idea

Most code knows exactly what it works with. Some code has to work with objects it has never seen: a plugin someone else wrote, a settings object, a function passed in by the caller. Introspection asks the object about itself: callable(x) (can it be called?), hasattr(obj, "name"), getattr(obj, "name", default) (read an attribute named by a string, with a fallback), setattr(obj, "name", value), vars(obj) (an object's attribute dictionary), and for functions inspect.signature(func), which describes every parameter.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · Asking objects about themselves
import inspect

class Product:
    def __init__(self, code, name):
        self.code = code
        self.name = name

    def label(self):
        return f"{self.name} ({self.code})"

def per_portion(value_per_100g, grams=30, *, round_to=None):
    return value_per_100g * grams / 100

p = Product("5010029000023", "Weetabix")
print(callable(p), callable(p.label), callable(per_portion))
print(getattr(p, "name"), getattr(p, "brand", "unknown brand"), hasattr(p, "label"))
setattr(p, "brand", "Weetabix Ltd")
print(vars(p))
sig = inspect.signature(per_portion)
for name, param in sig.parameters.items():
    print(name, param.kind.name, param.default if param.default is not param.empty else "required")

A typical use: a report runs plugin functions written by different teams, and each plugin takes only some of the available data. With inspect.signature, the report passes each plugin exactly the arguments it asks for, so plugins can be simple and the report doesn't need to know them in advance.

example-2.py · Call a plugin with what it asks for
import inspect

def call_with(func, available):
    params = inspect.signature(func).parameters
    wanted = {name: value for name, value in available.items() if name in params}
    return func(**wanted)

def sugar_warning(sugars_100g):
    return "high sugar" if sugars_100g > 22.5 else None

def fibre_claim(fiber_100g, product_name):
    return f"{product_name}: high fibre" if fiber_100g >= 6 else None

data = {"product_name": "All Bran Flakes", "sugars_100g": 11, "salt_100g": 0.28, "fiber_100g": 16}
for plugin in [sugar_warning, fibre_claim]:
    print(plugin.__name__, "->", call_with(plugin, data))

When to use it: frameworks and tools: plugin systems, command-line tools built from functions, serialisers, test runners, settings loaders. Mistakes to avoid: using introspection where ordinary code would do (getattr(obj, "name") with a fixed name is just obj.name); getattr without a default when the attribute may be missing; trusting attribute names that come from users without checking them; and setattr on names that start with _, which are private by convention.

Review
  • callable, hasattr, getattr(obj, name, default), setattr and vars examine and change objects by name.
  • inspect.signature(func).parameters describes each parameter: its kind and default (param.empty if none).
  • Use introspection for tools that handle unknown objects; ordinary code should just use the attribute.
  • Next: registries, which choose which function to run from data.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Introspection: examine objects while the program runs — Functional & Dynamic Python · Runtime