I am having trouble dealing with classes wherein I have classes whose attributes and methods I want to access in my API code, but not have those attributes and methods exposed to the user who is using the API.
Consider this code example:
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
The two functions are like operators that act on these objects, and it doesn't make sense to have them as methods to Foo
and Bar
in my application. Additionally they only act on certain types of the Foo
and Bar
classes. If I access foo._context
inside func_1
for example, mypy and pylance will yell at me due to accessing a private attribute. But I don't want to make it public since I don't want the users of the API to access this function. How can I overcome this while still keeping my code up to typing standards?
I understand that a simple way would be to simply access foo._context
inside the functions and include a typing ignore comment. However, this doesn't seem clean to me and I was wondering if there was a Pythonic way to do this. Along the same lines as this question, 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. I mean that these class attributes and methods should be public inside the package for developers but not for library users.
I'll preface this by saying that in real life I'd probably go with one of the two options you've already rejected, namely:
# pylint: disable
(or whatever) where needed. It's not philosophically any different from declaring a friend
class in a language that has a more strict public/private concept; the ability to declare exceptions to a general rule exists for a reason.There's also a good chance that in real life I'd be rethinking whether I wanted context
to be a class attribute, or whether there should be a single object called "context" (which often can imply a sort of "god object").
That said, another option is to make the classes themselves "private" to the module, and expose a public interface to them via a Protocol
or similar.
from typing import Protocol
class Context:
pass
class Foo(Protocol[T]):
pass # declare "public" attributes here
class _Foo(Foo[T]):
context: Context
class Bar(Protocol[T]):
pass # declare "public" attributes here
class _Bar(Bar[T]):
context: Context
def func_1(foo: Foo[str]) -> int:
assert isinstance(foo, _Foo)
# do something with _Foo.context
raise NotImplemented
def func_2(foo: Foo[int], bar: Bar[int]) -> Bar[str]:
assert isinstance(bar, _Bar)
assert isinstance(foo, _Foo)
# do something with _Foo.context/_Bar.context
raise NotImplemented