如何实现类属性API内公开外私有,及公共类内部专属实例化?
解决方案
一、包内访问私有属性(对外隐藏,类型检查合规)
1. 利用模块__all__与内部访问约定
单下划线开头的属性(如_context)是Python约定的模块级私有成员,同包内的模块访问这类属性时,mypy、Pylance等类型检查器默认不会报错。只需在类定义的模块中通过__all__控制对外导出的内容,仅暴露Foo、Bar等公共类,隐藏内部细节:
# core.py __all__ = ["Foo", "Bar"] class Context: pass class Foo(Generic[T]): _context: Context class Bar(Generic[T]): _context: Context # operations.py(同包内模块) from core import Foo, Bar def func_1(foo: Foo[str]) -> int: # 类型检查器允许同包内访问单下划线私有属性 do_something(foo._context) return 0
2. 使用typing.PrivateAttr(Python 3.10+)
用PrivateAttr明确标记属性为类私有成员,类型检查器会识别其私有特性,内部访问合法,外部访问会触发类型警告:
from typing import Generic, TypeVar, PrivateAttr T = TypeVar('T') class Context: pass class Foo(Generic[T]): _context: Context = PrivateAttr() class Bar(Generic[T]): _context: Context = PrivateAttr() def func_1(foo: Foo[str]) -> int: # 类型检查器认可内部对PrivateAttr的访问 process_context(foo._context) return 0
3. 内部协议标注(跨模块严格类型检查)
若操作函数与类不在同一模块,可定义仅内部可见的协议,声明允许访问_context,让操作函数的参数标注为该协议,同时通过__all__隐藏协议:
# core.py __all__ = ["Foo", "Bar"] from typing import Generic, TypeVar, Protocol T = TypeVar('T') class Context: pass # 仅内部可见的协议,声明拥有_context属性 class _HasContext(Protocol[T]): _context: Context class Foo(Generic[T], _HasContext[T]): _context: Context class Bar(Generic[T], _HasContext[T]): _context: Context # operations.py from core import _HasContext, Foo, Bar def func_1(foo: _HasContext[str]) -> int: # 类型检查器认可协议中的属性访问 handle_context(foo._context) return 0
二、阻止外部实例化公共类(内部可实例化)
1. 私有构造函数+内部工厂方法
将类的__init__设为双下划线私有(名称修饰后外部无法访问),同时提供内部可见的工厂方法创建实例,通过__all__隐藏工厂方法:
# core.py __all__ = ["Foo", "Bar"] from typing import Generic, TypeVar T = TypeVar('T') class Context: pass class Foo(Generic[T]): _context: Context # 双下划线构造函数,外部无法直接调用 def __init__(self, context: Context): self._context = context # 内部工厂方法,对外不导出 @classmethod def _create(cls, context: Context) -> "Foo[T]": return cls(context) # operations.py from core import Foo, Context def create_string_foo() -> Foo[str]: ctx = Context() # 内部调用工厂方法创建实例 return Foo._create(ctx)
2. 调用栈检查(严格限制实例化来源)
在__init__中检查调用者模块,仅允许内部包内模块实例化类:
import inspect from typing import Generic, TypeVar T = TypeVar('T') class Context: pass class Foo(Generic[T]): _context: Context def __init__(self, context: Context): # 获取调用者模块名 caller_module = inspect.stack()[1].module.__name__ # 限制仅内部包(如your_package)的模块可实例化 if not caller_module.startswith('your_package.'): raise RuntimeError("Cannot instantiate Foo directly. Use internal factory methods.") self._context = context
3. 元类控制(批量限制多类实例化)
通过元类拦截实例化请求,统一控制所有标记类的实例化权限:
import inspect from typing import Generic, TypeVar, Type T = TypeVar('T') class Context: pass class _InternalOnlyMeta(type): def __call__(cls, *args, **kwargs): caller_module = inspect.stack()[1].module.__name__ if not caller_module.startswith('your_package.'): raise RuntimeError("Cannot instantiate this class directly.") return super().__call__(*args, **kwargs) class Foo(Generic[T], metaclass=_InternalOnlyMeta): _context: Context def __init__(self, context: Context): self._context = context class Bar(Generic[T], metaclass=_InternalOnlyMeta): _context: Context def __init__(self, context: Context): self._context = context
内容的提问来源于stack exchange,提问作者person17381
相关产品推荐
相关产品推荐

