使类属性在 API 外部为用户私有,但在 API 内部为开发人员公开

问题描述 投票:0回答:2

我在处理类时遇到困难,其中我想在 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 python-typing mypy pylance pyright
2个回答
0
投票

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”的含义,您可以执行以下操作之一(请参阅分发类型信息的完整参考):

  • 如果“API内部公共”意味着“每个模块内部公共,但不跨模块”,那么你应该像这样布局你的包:
    example-project/
      example_package/
        __init__.py
        code_example.py
        code_example.pyi
        py.typed
    
  • 如果“public inside API”的意思是“在包内对开发人员公开,但不适用于库用户”,那么您将像这样布置包s,并确保库的用户也安装存根包:
    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
    )。

0
投票

您可以在允许您使用私有属性的类中定义

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
类的实例。

© www.soinside.com 2019 - 2024. All rights reserved.