我在处理类时遇到困难,其中我想在 API 代码中访问其属性和方法的类,但没有将这些属性和方法暴露给使用 API 的用户。
考虑这个代码示例:
class Context:
pass
class Foo(Generic[T]):
_context: Context
class Bar(Generic[T]):
_context: Context
def func_1(foo: Foo[str]) -> int:
# do something with _context
return NotImplemented
def func_2(foo: Foo[int], bar: Bar[int]) -> Bar[str]:
# do something with _context
return NotImplemented
这两个函数就像作用于这些对象的运算符,在我的应用程序中将它们作为
Foo
和 Bar
的方法是没有意义的。此外,它们仅作用于某些类型的 Foo
和 Bar
类。例如,如果我在 foo._context
内访问 func_1
,mypy 和 pylance 会因为访问私有属性而对我大喊大叫。但我不想公开它,因为我不希望 API 的用户访问这个功能。我怎样才能克服这个问题,同时仍然保持我的代码符合打字标准?
我知道一种简单的方法是简单地访问函数内的
foo._context
并包含输入忽略注释。然而,这对我来说似乎并不干净,我想知道是否有一种 Pythonic 方法可以做到这一点。与这个问题一样,我想知道是否有一种方法可以阻止 API 的用户实例化公共类,但仍然在 API 中以某种方式实例化。我的意思是,这些类属性和方法应该在包内为开发人员公开,而不是为库用户公开。
Python(不幸的是,IMO)不像其他语言那样具有复杂级别的访问修饰符。类型检查器本机支持的机制可以通过使用
.pyi
存根文件来执行类似的操作。
首先,您需要删除
_context
类中 .py
成员的前导下划线,以恢复其“公共”性。然后,您将维护一个 .pyi
存根,如下所示(注意类中没有 context
成员):
# code_example.pyi
class Foo(Generic[T]):
... # Other class members that you'd want to make public
class Bar(Generic[T]):
... # Other class members that you'd want to make public
# "I was wondering if there was a way to prevent the users of an API
# from instantiating a public class, but still instantiate somehow
# within the API."
class __DONT_USE_ME_ANYWHERE: ...
class Baz:
def __init__(self, unavailable_argument: __DONT_USE_ME_ANYWHERE) -> None:
"""
Instantiating this class is an error outside the API. Your
implementation of `Baz.__init__` in the corresponding `.py` file
has a proper (and maybe vastly different) signature.
"""
def func_1(foo: Foo[str]) -> int: ...
def func_2(foo: Foo[int], bar: Bar[int]) -> Bar[str]: ...
然后,根据“公共内部 API”的含义,您可以执行以下操作之一(请参阅分发类型信息的完整参考):
example-project/
example_package/
__init__.py
code_example.py
code_example.pyi
py.typed
example-project/
example_package/
__init__.py
code_example.py
example_package-stubs/ # This could be in another project instead
__init__.pyi
code_example.pyi
保持存根与实现同步需要进行一些维护,但是有一些方法可以减少所需的工作,例如
ast
writers 自动生成存根 - 最小实现可能只是擦除函数体并用省略号替换它们;partial\n
中添加行py.typed
)。您可以在允许您使用私有属性的类中定义
setters
和 getters
。一个例子是:
class Foo(Generic[T]):
self._context: Context
@property
def context(self):
return self._context
@context.setter
def context(self, text):
self._context = text
然后您应该能够在函数中执行类似
foo.context
的操作,其中 foo
是 Foo
类的实例。