Python String to Variable Name: Use Dictionaries Safely

Quick answer: A runtime string is usually data, not a Python variable declaration. Map it to a value with a dictionary, or use getattr() and setattr() on a controlled object after validating the name. globals(), locals(), and exec() make dependencies opaque and can create security or maintenance problems.

Python Pool infographic showing Python string key dictionary getattr setattr globals locals exec and safe dynamic names
Dynamic names are usually data: use a dictionary for a string-to-value mapping and reserve attribute access for a known object interface.

Turning text such as "total_score" into a Python name is usually a design choice, not just a syntax trick. A name written in source code and a key received at runtime solve different jobs. Source names are for the programmer. Runtime strings are data, so they normally belong in a dictionary, an object attribute, or an explicit namespace.

The safest answer is almost always a dictionary. It lets you create entries from user input, files, API fields, form labels, or generated column names without changing the surrounding program. It is also easy to inspect, test, serialize, and validate.

The official references for this guide are Python’s rules for identifiers, str.isidentifier(), globals(), locals(), exec(), and the setattr() built-in.

Use dynamic source names only when you control the names and there is a clear reason to expose them as attributes or module-level entries. If the name comes from a user, network response, spreadsheet, or config file, validate it first and store it in a container you own.

The examples below avoid hidden state and clean up after themselves where needed. They are written for reviewability: the data holder is explicit, invalid names are rejected, and generated entries can be listed without scanning a whole module.

Validate A Text Name First

A valid Python identifier must follow the language rules and must not be a reserved keyword. Check both before using a string as a name-like value.

import keyword

def is_safe_name(text):
    return text.isidentifier() and not keyword.iskeyword(text)

names = ["total_score", "2fast", "class", "user_id"]

for item in names:
    print(item, is_safe_name(item))

This check protects your code from names that cannot appear in Python source. It also gives you one place to enforce a naming policy, such as lower-case names, known prefixes, or a fixed allow-list.

Validation is not a permission system by itself. If a string came from outside your program, combine identifier checks with an allow-list and a clear destination container.

Use A Dictionary For Runtime Names

A dictionary is the right tool when names are discovered while the program runs. It keeps dynamic entries separate from source code names and makes all created entries visible in one place.

pairs = [
    ("total_score", 96),
    ("max_level", 12),
    ("active_users", 58),
]

values = {}
for name, amount in pairs:
    values[name] = amount

print(values["total_score"])
print(sorted(values))

This pattern is better than creating many top-level names. You can loop over the entries, serialize them as JSON, test them with ordinary assertions, and pass the container into other functions.

Use this approach for parsed files, report columns, form fields, metrics, settings, and any case where the number of entries is not known when the code is written.

Python Pool infographic showing a string key, dictionary, value, and safe dynamic lookup
A dictionary is the safe default for mapping strings to values or callables.

Use globals() Only In Controlled Scripts

globals() exposes the module namespace as a dictionary. Assigning into it can create a module-level name, but that should be limited to small controlled scripts or compatibility code.

name = "report_total"

if name.isidentifier():
    globals()[name] = 125

print(report_total)

del globals()[name]

The example removes the generated name afterward so the module does not keep surprise state. In application code, a normal dictionary is usually clearer because it does not mix generated data with imports, functions, and constants.

Avoid writing untrusted names into globals(). Even a valid identifier can overwrite something important if you do not check it against an allow-list.

Use exec() Only With Trusted Source

exec() executes Python source text. It can create names in a supplied namespace, but it should not be used on user input or unreviewed strings.

source = "answer = base * rate"
scope = {"base": 21, "rate": 2}

exec(source, {}, scope)

print(scope["answer"])
print(sorted(scope))

Passing a dedicated namespace is safer than letting executed text write into the surrounding module. It also makes the result easy to inspect and discard.

Use exec() only when you truly need to run Python source text that your program controls. For most data mapping jobs, dictionaries, dataclasses, or objects are simpler and safer.

Python Pool infographic comparing an object, string attribute, getattr, and returned value
getattr supports controlled dynamic attribute access when names are validated.

Read locals(), Do Not Depend On Writing It

locals() is useful for inspecting the current local namespace. It is not a reliable way to create local names inside a function. Build an explicit dictionary instead.

def describe(name, score):
    snapshot = locals()
    return f"{snapshot['name']} scored {snapshot['score']}"

def make_record(field, value):
    record = {}
    record[field] = value
    return record

print(describe("Ada", 98))
print(make_record("level", 7))

The first function reads the current local namespace for display. The second function stores dynamic data in a dictionary, which is predictable and portable across Python implementations.

If you need to return many named values from a function, return a dictionary, dataclass, named tuple, or object. That keeps the contract explicit for callers.

Use setattr() For Object Attributes

When the destination is an object, setattr() can assign an attribute whose name is stored as text. This is useful for small record objects, adapters, and controlled mappings from known field names.

from types import SimpleNamespace

record = SimpleNamespace()

for name, value in [("status", "ready"), ("count", 3)]:
    setattr(record, name, value)

print(record.status)
print(vars(record))

Use setattr() only after checking which attribute names are allowed. Without that check, generated names can replace existing attributes or create a confusing object API.

For many data-heavy tasks, vars(record) or a plain dictionary is easier to pass around. Choose attributes when dot access is part of a clear object design.

In short, validate text names, prefer dictionaries for runtime-created entries, avoid writing to locals(), limit globals() to controlled scripts, use exec() only with trusted source text, and use setattr() when a known object really should receive an attribute.

Use A Dictionary For Dynamic Data

A dictionary makes the mapping from an external name to a value explicit, supports membership checks and defaults, and is easy to serialize or test. It also avoids collisions with real module variables and keeps the set of accepted names visible.

Python Pool infographic comparing string name, globals, dynamic assignment, and safer mapping
Mutating globals or locals from user-controlled names is harder to reason about and secure.

Validate External Names

Normalize case and whitespace only when the application contract allows it, then check the name against an allowlist or a known schema. Reject unexpected keys before they select behavior, access a resource, or change configuration. A valid Python identifier is not automatically a valid application key.

Use Object Attributes Deliberately

getattr(obj, name, default) and setattr(obj, name, value) are appropriate when the string refers to a defined object interface. Restrict which attributes can be read or changed, avoid overwriting methods accidentally, and make the allowed names part of the class contract.

Python Pool infographic testing untrusted names, collisions, validation, namespaces, and validation
Check name validation, collisions, scope, serialization, and whether a dictionary is clearer.

Treat globals(), locals(), And exec() With Care

Changing globals can couple unrelated code through hidden state, and writing to locals is not a reliable way to create local variables inside a function. exec() evaluates source code and must never receive untrusted input. These tools belong only in tightly controlled, reviewed metaprogramming cases.

Test Names As A Public Boundary

Test supported names, missing names, duplicates, invalid identifiers, reserved behavior, malicious strings, and default handling. Assert that a lookup cannot modify an unrelated value or call an unintended method. Explicit data structures usually make these guarantees easier to prove.

The official getattr, setattr, globals, and exec references document their behavior and risks. Related guidance includes lookup tables and boundary tests.

For related dynamic access, compare explicit mappings, attribute checks, and boundary tests before allowing a runtime name to change behavior.

Frequently Asked Questions

How do I use a string as a variable name in Python?

Usually use a dictionary that maps the string to its value; this is explicit, testable, and avoids changing the local namespace dynamically.

Can I use globals() to create a variable?

You can in a controlled script, but it couples data to module state and makes dependencies difficult to discover; a dictionary is usually safer.

Is exec() safe for dynamic variable names?

Only with fully trusted, carefully constrained source. Never pass untrusted strings to exec(), and prefer data structures or setattr() on a controlled object.

How do I set a dynamic object attribute?

Use setattr(obj, name, value) after validating the name and limiting which attributes the caller may change; use getattr() for retrieval with an explicit default when appropriate.

Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Audrey Vasquez
Audrey Vasquez
3 years ago

Hi!
I appreciate this help. I’m using the globals() option, which is working but it’s still printing out my initial string rather than the new set variable. although when i print out the actual variable (by calling the name of it that the string is) it prints out the value i set it to. so it works, but for purposes of making it more universal i didnt plan on writing out what the string is because i want it to be used for many different types of strings, any ideas? thank you!