Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 18 – Advanced Python: data model, metaprogramming and internals

Inside the import system

Follow a Python import through sys.modules, finders, specs and loaders, import plugins by name with importlib, and read package data and installed versions.

  • Advanced
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: An import looks in sys.modules before it searches anywhere · A finder returns a spec, and a loader runs the module · Importing a module whose name is known only at run time · Practise: a plugin loader, then check yourself. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Trace an import through sys.modules, the finders on sys.meta_path and the loader that runs the module
  • Read a module's spec with importlib.util.find_spec() and say what its loader and origin mean
  • Import plugins by name with importlib.import_module() and report each failure without stopping
  • Write a meta path finder that serves or blocks modules, and place it on sys.meta_path
  • Read data files and installed versions with importlib.resources and importlib.metadata

Before you start

What you’ll learn · Before you start

What you will learn

  • Trace an import through sys.modules, the finders on sys.meta_path and the loader that runs the module
  • Read a module's spec with importlib.util.find_spec() and say what its loader and origin mean
  • Import plugins by name with importlib.import_module() and report each failure without stopping
  • Write a meta path finder that serves or blocks modules, and place it on sys.meta_path
  • Read data files and installed versions with importlib.resources and importlib.metadata

Before you start

On this page

The first import greeting in a program goes through a cache, a list of finders and a loader before the module’s first line runs; the second import greeting is a single dictionary lookup. Knowing those steps explains why a module imported twice runs once, why a plugin system can load the wrong file, and what each kind of import error means.

Below you will watch the finders being asked, read the specs of five modules, and build a plugin loader that reports each broken plugin instead of stopping at the first.

An import looks in sys.modules before it searches anywhere

import greeting first looks for the name in sys.modules, the module cache: The dictionary sys.modules, which maps the name of every module a program has imported to its module object. An import looks there first, which is why a module's file runs only once per program. More in “Modules, imports and __name__”. Only on a miss does Python search: it asks each finder: An object the import system asks to find a module by name. It answers with a module spec that says how to load the module, or with None when the module is not one it knows; the finders on sys.meta_path are asked in order. on sys.meta_path: The list of finders that an import asks, in order, for a module that is not in sys.modules yet. By default it holds one finder for built-in modules, one for frozen modules and one for the folders of sys.path. in turn, and the first one that knows the module returns a module spec: A ModuleSpec object: what a finder knows about a module, such as its name, its loader, where it comes from (origin) and, for a package, where its submodules are. A module keeps its spec as __spec__. naming a loader: The object that turns a module spec into a working module: the import system calls its create_module() to make the module object and its exec_module() to run the module's code in it.. The loader creates the module, the module is added to sys.modules, and only then does its code run. Step through a first and a second import:

Interactive · stage by stage The first and the second import of greeting Step through the two imports of main.py. The branch on the right is the short way a second import takes.
The first and the second import of greeting, step 8 of 8. Showing import greeting, In sys.modules? (yes: greeting), Ask each finder (Watcher says None, next), A spec comes back (origin greeting.py), Loader creates module, Added to sys.modules (greeting is in the cache), Loader runs the code (printed: greeting.py runs), Name is bound. In focus: In sys.modules?, Name is bound.yes: no searchimport greetingthe statement runsIn sys.modules?yes: greetingAsk each finderWatcher says None, nextA spec comes backorigin greeting.pyLoader creates modulean empty module objectAdded to sys.modulesgreeting is in the cacheLoader runs the codeprinted: greeting.py runsName is boundgreeting = the modulefound in the cache

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Look in the cache. The first import greeting looks up the name in sys.modules. It is not there yet, so the search begins.
  2. The finders, in order. Python asks each finder on sys.meta_path in turn. The Watcher of main.py is first and prints that it was asked, then returns None, so the next finder is asked.
  3. The path finder knows it. The path finder searches the folders of sys.path and finds greeting.py beside main.py. It returns a spec whose loader is a SourceFileLoader.
  4. An empty module. The loader makes an empty module object, and the import system sets its __name__, __file__ and __spec__ from the spec.
  5. Registered first. The module goes into sys.modules before any of its code runs, so a module that imports greeting back meets this half-made module instead of loading a second copy.
  6. The code runs. exec_module() runs greeting.py in the new module's namespace. That printed the line "greeting.py runs" and defined hello().
  7. The name is bound. The import statement binds the name greeting in main.py to the module, and the first import is done.
  8. The second import. The second import greeting finds the module in sys.modules and binds the name at once. No finder is asked and greeting.py does not run again, which is why main.py printed nothing between its two headings.

The program puts a finder of its own at the front of sys.meta_path, one that only reports each name it is asked for:

Two imports of greeting, with the finders watched

steps/main.py

import sys


class Watcher:
    """A finder that only reports the names it is asked for."""

    def find_spec(self, name, path, target=None):
        print(f"  finders asked for {name!r}")
        return None  # not mine: the next finder on sys.meta_path answers


sys.meta_path.insert(0, Watcher())
print("first import:")
import greeting

print("second import:")
import greeting  # noqa: F811

print("cached:", sys.modules["greeting"] is greeting)
print(greeting.hello("Asha"))

Output

first import:
  finders asked for 'greeting'
  greeting.py runs
second import:
cached: True
Hello, Asha

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 main.py

steps/greeting.py

print("  greeting.py runs")


def hello(name):
    return f"Hello, {name}"

The finder was asked once and greeting.py ran once; the second import found the module in sys.modules and asked nobody. Because the module is in the cache before its code runs, a circular import: Two or more modules that import each other. It fails when one of them asks for a name that the other has not defined yet, because that module is still running its first lines. More in “Modules, imports and __name__” meets a half-built module instead of loading a second copy. And if the module’s code raises, the import system takes it out of sys.modules again, so a later import starts afresh.

Checkpoint

Check: The steps of an import

  1. Question 1 of 2 main.py is changed to run import greeting three times. How many "finders asked" lines does the Watcher print?

    Type a number.

    A hint

    Where does the second import look first?

    Show the answer: Question 1 of 2

    Answer: 1 lines

    Only the first import misses the cache and asks the finders. The second and third find greeting in sys.modules and bind the name at once, so the Watcher prints one line and greeting.py runs once.

  2. Question 2 of 2 When is a newly found module added to sys.modules?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: After the loader creates it and before its code runs

    The loader creates the module, the import system puts it into sys.modules, and only then does exec_module() run its code. If the code raises, the module is taken out again.

A finder returns a spec, and a loader runs the module

importlib.util.find_spec(name) asks the same finders without importing the module and returns the spec, or None when no finder knows the name; for a dotted name such as json.decoder it does import the parent package first. A spec records what the import system needs: the module’s name, its loader, its origin (a file, built-in or frozen) and, for a package, where its submodules are. By default sys.meta_path holds three finders: one for modules built into the interpreter, one for frozen modules and one that searches the folders of sys.path.

The specs of five modules

specs/specs.py

import importlib.util
import pathlib

for name in ["json", "sys", "__hello__", "ledger", "no_such_module"]:
    spec = importlib.util.find_spec(name)  # finds the module, imports nothing
    if spec is None:
        print(f"{name}: no spec, so importing it raises ModuleNotFoundError")
        continue
    loader = spec.loader if isinstance(spec.loader, type) else type(spec.loader)
    origin = pathlib.Path(spec.origin).name if spec.has_location else spec.origin
    kind = "package" if spec.submodule_search_locations is not None else "module"
    print(f"{name}: {kind}, loader {loader.__name__}, origin {origin}")

Output

json: package, loader SourceFileLoader, origin __init__.py
sys: module, loader BuiltinImporter, origin built-in
__hello__: module, loader FrozenImporter, origin frozen
ledger: module, loader SourceFileLoader, origin ledger.py
no_such_module: no spec, so importing it raises ModuleNotFoundError

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 specs.py

specs/ledger.py

BALANCE = 0

Version note

In the browser, Python’s standard library is kept in one zip file, so json is found there and loaded by zipimporter, the loader for modules inside zip archives. The steps of the import are the same; only the loader differs.

In your browser, Pyodide 314.0.7 (CPython 3.14.2) prints:

json: package, loader zipimporter, origin __init__.py
sys: module, loader BuiltinImporter, origin built-in
__hello__: module, loader FrozenImporter, origin frozen
ledger: module, loader SourceFileLoader, origin ledger.py
no_such_module: no spec, so importing it raises ModuleNotFoundError

sys is compiled into the interpreter and __hello__ is a frozen module, whose code is stored inside the interpreter too, so neither has a file. json is a package and ledger is the file beside the script. A module that has been imported keeps its spec as module.__spec__, and its __file__, __loader__ and __package__ are set from it.

Checkpoint

Check: Module specs and loaders

  1. Question 1 of 2 importlib.util.find_spec("reportz") returns None. What would import reportz do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Raise ModuleNotFoundError

    find_spec() asks the same finders an import asks, so None means the import would raise ModuleNotFoundError.

  2. Question 2 of 2 What did specs.py print as the loader and origin of sys?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: BuiltinImporter, origin built-in

    The default meta path holds a finder for built-in modules, one for frozen modules and the path finder. sys is a built-in module, so its spec names BuiltinImporter and its origin is the string built-in.

Importing a module whose name is known only at run time

A plugin system reads module names from a setting, which the import statement cannot use, because its names are written in the code. importlib.import_module(name) takes the name as a string and returns that module, for a dotted name the submodule itself, where __import__() would return the top-level package. This loader imports four plugins and keeps going when one fails:

Four plugins imported by name, three failures told apart

plugins/main.py

import importlib


def load_plugins(names):
    """Imports each plugin by name; one that fails is reported, not fatal."""
    loaded, errors = {}, {}
    for name in names:
        try:
            loaded[name] = importlib.import_module(name)
        except ModuleNotFoundError as error:
            if error.name == name:
                errors[name] = "no such module"
            else:  # the plugin exists, but something it imports does not
                errors[name] = f"needs {error.name}"
        except Exception as error:  # the plugin's own code failed
            errors[name] = f"{type(error).__name__}: {error}"
    return loaded, errors


names = ["plugins.csv_export", "plugins.pdf_export", "plugins.broken", "plugins.xml_export"]
loaded, errors = load_plugins(names)
for name, module in loaded.items():
    print(f"loaded {name}: {module.FORMAT}")
for name, reason in errors.items():
    print(f"failed {name}: {reason}")

Output

loaded plugins.csv_export: CSV
failed plugins.pdf_export: needs fancy_pdf
failed plugins.broken: ValueError: invalid literal for int() with base 10: 'A4'
failed plugins.xml_export: no such module

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 main.py

plugins/__init__.py · plugins/plugins/__init__.py

"""Export plugins: each module of this package adds one export format."""

plugins/plugins/csv_export.py

FORMAT = "CSV"

plugins/plugins/pdf_export.py

import fancy_pdf  # a library this machine does not have

FORMAT = "PDF"

plugins/plugins/broken.py

FORMAT = "XLSX"
PAGE_SIZE = int("A4")  # a bug in the plugin itself

The three failures are three different problems. plugins.xml_export does not exist, plugins.pdf_export exists but imports fancy_pdf, which this machine lacks, and plugins.broken runs and raises a ValueError of its own. The name of a ModuleNotFoundError tells the first two apart: it is the plugin’s own name only when the plugin itself is missing.

Reporting every ImportError as a missing plugin

except ImportError: print(name, "not installed") reports a plugin that is installed but needs another library as not installed, and sends someone to install what is already there. Compare error.name with the name you imported.

A loader that tells the cases apart turns a vague import error into the right fix: correct the plugin’s name, install the library it needs, or repair the plugin’s own code.

Key idea

Every import asks sys.modules first, then the finders for a spec, then the spec’s loader, and the step that failed says what to fix.

Checkpoint

Check: Importing by name

  1. Question 1 of 2 What does importlib.import_module("plugins.csv_export") return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The module plugins.csv_export

    The documentation contrasts the two: import_module() returns the specified package or module, while __import__() returns the top-level package.

  2. Question 2 of 2 Importing plugins.pdf_export raised ModuleNotFoundError. What was its name?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 'fancy_pdf'

    ModuleNotFoundError.name is the module that was missing. Comparing it with the name you imported tells a missing plugin from a plugin whose dependency is missing.

Python Online Compiler Call importlib.util.find_spec() on the modules you use most and compare their loaders and origins.

Practise: a plugin loader, then check yourself

The exercise asks for the loader of this lesson in the shape a plugin system needs, with the three kinds of failure told apart. Its sample tests write their plugins to a temporary folder and run in your browser.

Exercise · Medium · Python

A plugin loader that reports every failure

load_plugins(names) in loader.py imports each module named in the list names and returns two dictionaries, (loaded, errors), in the order of names:

  • loaded maps each name that imported to its module. For a dotted name such as "tools.text" that is the module tools.text itself, not the package tools.
  • errors maps each name that failed to a short reason: "missing" when that module does not exist, "needs <module>" when it exists but a module it imports does not, and the class name of the exception, such as "ValueError", for any other error the plugin's own code raises.

One failing plugin never stops the others. The tests write their plugins to a temporary folder first.

Starter code · loader.py

def load_plugins(names):
    """Imports each named module and returns (loaded, errors), never stopping at a failure."""
    loaded, errors = {}, {}
    for name in names:
        try:
            loaded[name] = __import__(name)
        except ImportError:
            errors[name] = "missing"
    return loaded, errors
The sample tests · test_loader.py
import importlib
import sys
import tempfile
from pathlib import Path

from loader import load_plugins

FOLDER = Path(tempfile.mkdtemp())
FILES = {
    "plug_a.py": "NAME = 'a'\n",
    "plug_b.py": "NAME = 'b'\n",
    "plug_needs.py": "import plug_library_not_here\nNAME = 'needs'\n",
    "plug_broken.py": "NAME = 'broken'\nraise ValueError('bad setting')\n",
    "plug_pkg/__init__.py": "",
    "plug_pkg/part.py": "NAME = 'part'\n",
}
for file, text in FILES.items():
    (FOLDER / file).parent.mkdir(parents=True, exist_ok=True)
    (FOLDER / file).write_text(text)
sys.path.insert(0, str(FOLDER))
importlib.invalidate_caches()


def test_loads_good_plugins():
    """plugins that import are returned as modules, with no errors"""
    loaded, errors = load_plugins(["plug_a", "plug_b"])
    assert [m.NAME for m in loaded.values()] == ["a", "b"]
    assert errors == {}


def test_dotted_name_gives_the_module_itself():
    """a dotted name returns the submodule, not its package"""
    loaded, _ = load_plugins(["plug_pkg.part"])
    assert loaded["plug_pkg.part"].__name__ == "plug_pkg.part"


def test_missing_plugin():
    """a module that does not exist is reported as missing"""
    assert load_plugins(["plug_not_written"]) == ({}, {"plug_not_written": "missing"})


def test_missing_dependency():
    """a plugin that imports a missing module names that module"""
    _, errors = load_plugins(["plug_needs"])
    assert errors == {"plug_needs": "needs plug_library_not_here"}


def test_plugin_raises():
    """an exception from the plugin's own code is reported by its class name"""
    _, errors = load_plugins(["plug_broken"])
    assert errors == {"plug_broken": "ValueError"}


def test_one_failure_does_not_stop_the_others():
    """every name is tried, in order, whatever fails before it"""
    loaded, errors = load_plugins(["plug_broken", "plug_a", "plug_not_written", "plug_b"])
    assert list(loaded) == ["plug_a", "plug_b"]
    assert list(errors) == ["plug_broken", "plug_not_written"]
A hint

__import__("tools.text") returns the package tools, which is why the documentation points to importlib.import_module() for imports by name. To tell a missing plugin from a plugin with a missing dependency, look at the name attribute of the ModuleNotFoundError: which module could not be found?

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then check the import steps, specs and imports by name: every choice says why it is right or what it misses, and a missed idea is asked again.

Practice that adapts to you

Practise this lesson

9 questions about 11 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 Where does an import look first?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: In sys.modules

    The import system reference names sys.modules as the first place an import checks, which is why a module's code runs once per program.

  2. Question 2 of 9 The first import settings fails with a ValueError raised by settings.py. What happens at a second import settings?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: The module's code runs again, because the failed module was taken out of sys.modules

    If loading fails, the failing module, and only that module, is removed from sys.modules, so a later import runs its code again.

  3. Question 3 of 9 orders.py imports billing, and billing.py imports orders. When the program first imports orders, what does billing get from its import of orders?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: The half-built orders module that is already in sys.modules

    The import system reference puts a module into sys.modules before its code runs, so a circular import meets the half-built module. Names that orders defines after its import of billing are not there yet when billing runs.

  4. Question 4 of 9 What does a module spec's origin hold for a module loaded from a file?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: Where the module was loaded from, the file's path

    A spec holds the module's name, loader and origin; for a module from a file, origin is the file's path, and for sys it is the string built-in.

  5. Question 5 of 9 A start-up check must report whether the optional module yaml is installed, without importing it. What does it call?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: importlib.util.find_spec("yaml"), and checks for None

    The documentation recommends find_spec() to check whether a module can be imported without importing it. For a dotted name it does import the parent package.

  6. Question 6 of 9 In the browser, json was loaded by zipimporter, on this machine by SourceFileLoader. What does that show?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: The loader is chosen for each module by the finder that finds it, and the import steps stay the same

    Finders and loaders are plug-ins of the import system: where modules live decides which loader runs them, while the cache, the finders and the spec work the same.

  7. Question 7 of 9 Why can a plugin system not use the import statement for plugin names read from a settings file?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: The import statement needs the name written in the code

    import plugins.csv_export spells the name in the source. A name held in a string, from a setting or a command line, is imported with importlib.import_module(name).

  8. Question 8 of 9 With this handler, what is reported for a plugin reports.pdf whose first line is import fancy_pdf, on a machine without fancy_pdf?

    Read the code, then choose one answer.

    try:
        importlib.import_module(name)
    except ModuleNotFoundError as error:
        reason = "missing" if error.name == name else f"needs {error.name}"
    Show the answer: Question 8 of 9

    Answer: needs fancy_pdf

    The handler compares error.name, the module the import system could not find, with the plugin's own name, which tells a missing dependency from a missing plugin.

  9. Question 9 of 9 A service loads 30 plugins at start-up. Which behaviours should its loader have?

    Choose every answer that is right.

    Show the answer: Question 9 of 9

    Answer:

    • Tell a missing plugin from one whose dependency is missing
    • Report each failing plugin with its reason and go on with the rest

    A good loader keeps going, labels each failure by its cause, and makes the failures visible, as the lesson's loader did for its three broken plugins.

Finders and loaders of your own, and loading a file by its path Pro: opens with a Pro, Premium or Ultimate pass

The import system is open: a finder of your own on sys.meta_path can serve modules from anywhere, and its loader decides how their code runs. This part builds a finder that serves modules from a dictionary, shows that its place on the list decides which module wins, and reproduces what breaks when a file is loaded by path without registering it.

In this part:

  • A meta path finder that serves modules from a dictionary of source text
  • The first finder in the meta path wins: a dictionary module and a file of one name
  • Loading a file by its path, and the pickle that fails without sys.modules
  • Lazy loading with LazyLoader, and the error that moves to the first use
  • Exercise: a finder that makes a module look missing
  • A finder that makes a module look missing
    • Results of the sample tests

4 runnable examples (Python, greet.py, lazy_import.py, load_by_path.py, order.py, plugins/invoice.py, report.py) · 1 exercise with sample tests · about 9 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Reloading, package data and installed metadata, each failure reproduced Pro: opens with a Pro, Premium or Ultimate pass

Three parts of importlib solve problems that file paths and restarts cannot. This part reproduces what importlib.reload() leaves behind, reads a data file that sits inside a package even when the package is in a zip file, and finds installed versions and plugins through their metadata.

In this part:

  • What importlib.reload() keeps: old objects beside new code, reproduced
  • Rules that change without a restart: reload checked data, not code
  • Package data that works from a folder and from a zip: importlib.resources
  • Installed versions and plugins from metadata, with a .dist-info written by hand

4 runnable examples (Python, csv_export.py, demo_plugins/__init__.py, installed.py, json_export.py, read_units.py, units/__init__.py, units/factors.json) · about 7 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Mid to senior

    After a deploy, a worker still behaves as if reports.py were the old version: a function added in the new version is missing, although the new file is on disk. How do you find which code the worker is running and why, and how do you stop it happening again?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    Your team wants pricing rules, written as a Python module, to change in a long-running service without a restart, by calling importlib.reload() when the file changes. What breaks, and what would you build instead?

    Model answer, Pro. First step and follow-ups

  3. Mid to senior

    A command-line tool takes 1.8 seconds before it prints --help, and users notice. How do you find which imports cost the time, and what would you change, on Python 3.14 and on 3.15?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Every import asks sys.modules first, so a module’s code runs once per program, and the module is in the cache before its code runs.
  • On a miss, the finders on sys.meta_path are asked in order for a spec, and the spec’s loader creates and runs the module.
  • importlib.util.find_spec() shows where a module would come from without importing it.
  • importlib.import_module() imports by name, and the name of a ModuleNotFoundError says which module was missing.

Terms in this lesson

finder
An object the import system asks to find a module by name. It answers with a module spec that says how to load the module, or with None when the module is not one it knows; the finders on sys.meta_path are asked in order.
loader
The object that turns a module spec into a working module: the import system calls its create_module() to make the module object and its exec_module() to run the module's code in it.
module spec
A ModuleSpec object: what a finder knows about a module, such as its name, its loader, where it comes from (origin) and, for a package, where its submodules are. A module keeps its spec as __spec__.
sys.meta_path
The list of finders that an import asks, in order, for a module that is not in sys.modules yet. By default it holds one finder for built-in modules, one for frozen modules and one for the folders of sys.path.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.