You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python函数内定义全局自定义类型:globals()使用安全性疑问

Is 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 namespace parameter lets you explicitly set where the type is stored (e.g., pass MyClass.__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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 18:54:05