Python函数内定义全局自定义类型:globals()使用安全性疑问
globals()[name] = new_tp Safe? And How Does It Behave in Edge Cases? Hey there! First off, nice work building that new_type wrapper to fix the DRY and ergonomic issues with vanilla NewType—that's a smart way to streamline creating typed aliases with extra metadata. Let's dig into your question about the globals() line.
What globals() Actually Does
First, a quick recap: globals() returns the module-level global namespace dictionary for the current module. When you call new_type at the top level of your module, this works exactly as you want—your new type gets added to the module's global scope, just like if you'd defined it with Key = NewType(...) directly.
But things get tricky when you step outside the module top-level. Here are the key edge cases to watch for:
1. Nested Functions: Polluting the Global Scope
If you call new_type inside a nested function, globals() still points to the module's global namespace—not the function's local scope. For example:
def setup_types(): new_type('InnerKey', str) setup_types() # InnerKey ends up in the module's global scope, not setup_types' local scope
This means you can't create types that are only visible inside the nested function—everything gets dumped into the global namespace, which can lead to naming collisions if you reuse type names across different functions.
2. Class Definitions: Types End Up Globally, Not in the Class
Same problem applies if you call new_type inside a class definition. Instead of the type becoming an attribute of the class, it gets added to the module's global scope:
class DataProcessor: new_type('ClassKey', int) # You'd expect DataProcessor.ClassKey, but instead you get a global ClassKey
That's almost certainly not what you want if you're trying to define class-specific types.
3. Safety Risks: Accidental Overwrites
The biggest safety hazard here is unintentionally overwriting existing names in the global scope. If you pass a name that's already used by a variable, function, or other type in your module, globals()[name] = new_tp will silently replace it. This can cause weird, hard-to-debug bugs later on.
A Better Approach: Control the Scope
If you want your new_type function to work reliably across different scopes, you can make it accept an optional namespace parameter, or automatically detect the caller's local scope using the inspect module. Here's how to adjust your function:
from typing import Any, NewType, Union, List, Iterable, Optional import inspect def new_type(name, tp, doc: Optional[str]=None, aka: Optional[Iterable]=None, namespace=None): """Make a new type with (optional) doc and (optional) aka, set of var names it often appears as""" new_tp = NewType(name, tp) if doc is not None: setattr(new_tp, '__doc__', doc) if aka is not None: setattr(new_tp, '_aka', set(aka)) # Determine where to store the new type if namespace is None: # Get the caller's stack frame to access their local scope caller_frame = inspect.currentframe().f_back namespace = caller_frame.f_locals # Clean up the frame reference to avoid memory leaks del caller_frame # Add a safety check to avoid overwriting existing names if name in namespace: raise ValueError(f"Name '{name}' already exists in the target namespace!") namespace[name] = new_tp
This version fixes the scope issues:
- When called at module top-level, it uses the module's global scope (same as before)
- When called inside a nested function, it adds the type to the function's local scope
- When called inside a class, it adds the type as a class attribute
- The optional
namespaceparameter lets you explicitly set where the type is stored (e.g., passMyClass.__dict__to add it to the class) - The safety check prevents accidental overwrites of existing names
A quick note: inspect.currentframe() might not work in some optimized Python environments (like PyPy with certain flags) or sandboxes, but for most standard Python use cases, it's reliable.
Small Extra Tip
You could also default aka to an empty set instead of None to simplify the internal logic—just adjust the parameter to aka: Iterable = () and set setattr(new_tp, '_aka', set(aka)) without the if check. But that's just a stylistic choice based on your needs.
内容的提问来源于stack exchange,提问作者thorwhalen

