You're holding it (Python) wrong. Python OO was a counter reaction to the bondage and discipline that languages like C++ had with private members and protected inheritance.
If you have members that users probably shouldn't touch, you prepend them with an underscore. This is just a hint; It doesn't actually change anything. We're all adults here and we know the consequences of reaching into implementation details.
show comments
gorgoiler
An alternative to consider might be to accept a Literal[“fast”, “slow”] or an Enum FAST or SLOW, and then decode that into shipping options inside the shipping code.
Only then are you truly putting a solid boundary between your library and the folks using your library. Everything else is just praying that you and only you have an underscore on your keyboard! :)
And of course another alternative is to accept that there is no true private in Python other than defdef*, so you allow your ShippingOption to be publicly visible while also documenting that the helper-constructors are what should really be used.
*”defdef” as in function definitions inside other function definitions — closures if you will, although I prefer to write mine as taking most if not all their parameters explicitly:
def public(foo):
def private(foo):
…
class Private:
… # less common
…
nayuki
Java made opaque types possible from the very start by private and package-private constructors.
It's sad to see that many features regarding object-oriented programming and static typing are implemented worse in Python than Java. Various examples: __str__() vs. toString(); underscore vs. private; @staticmethod/@classmethod vs. static; generic types are so clunky in Python; types are not shown in the official Python standand library documentation; __init__() doesn't force you to call super() whereas it's mandatory in Java; @override (Python 3.12; year 2023) copying Java @Override (JDK 1.5; year 2004) very late; convention changing from duck typing (always available in Python) to structural typing (optional in Python, mandatory in Java).
show comments
CreRecombinase
Why not just use a dictionary, or why not just leave the type unannotated? If you really can't (or don't want to) say anything about the type, then don't. Python is dynamically typed!
show comments
sdeframond
Funny, I ran into the same pattern just a few months ago!
In practice, I found it difficult for coworkers to read and understand so I dropped the idea.
Another limitation I found is that it breaks down when you start using inheritance. For example:
```
class _A: pass
A = NewType("A", _A)
class _B(_A): pass
B = NewType("B", _B)
def foo(a: A) -> None:
pass
b = B(_B())
foo(b) # Mypy is not happy: Argument 1 to "foo" has incompatible type "B"; expected "A"
foo(A(b)) # Mypy is OK
```
show comments
corwinxpro
The main problem with such approach is that `class _RealShipOpts:` is very ugly to write unit tests for. You need to import a private entity in tests. I would slightly change the presented approach, and move the "public" `ShippingOptions`, `shipFast`, etc., into a new module that is a public API, for my users to use something like `from my_lib.shipping.api import ShippingOptions`.
That way, I can use "normal" naming in `class RealShipOpts:...`, and be explicit that it's not really public for the end users (they should use the `.api` module instead).
tcdent
I'm sorry but if you write Python functions/methods in camel case I can't take you seriously.
You're holding it (Python) wrong. Python OO was a counter reaction to the bondage and discipline that languages like C++ had with private members and protected inheritance.
If you have members that users probably shouldn't touch, you prepend them with an underscore. This is just a hint; It doesn't actually change anything. We're all adults here and we know the consequences of reaching into implementation details.
An alternative to consider might be to accept a Literal[“fast”, “slow”] or an Enum FAST or SLOW, and then decode that into shipping options inside the shipping code.
Only then are you truly putting a solid boundary between your library and the folks using your library. Everything else is just praying that you and only you have an underscore on your keyboard! :)
And of course another alternative is to accept that there is no true private in Python other than defdef*, so you allow your ShippingOption to be publicly visible while also documenting that the helper-constructors are what should really be used.
*”defdef” as in function definitions inside other function definitions — closures if you will, although I prefer to write mine as taking most if not all their parameters explicitly:
Java made opaque types possible from the very start by private and package-private constructors.
It's sad to see that many features regarding object-oriented programming and static typing are implemented worse in Python than Java. Various examples: __str__() vs. toString(); underscore vs. private; @staticmethod/@classmethod vs. static; generic types are so clunky in Python; types are not shown in the official Python standand library documentation; __init__() doesn't force you to call super() whereas it's mandatory in Java; @override (Python 3.12; year 2023) copying Java @Override (JDK 1.5; year 2004) very late; convention changing from duck typing (always available in Python) to structural typing (optional in Python, mandatory in Java).
Why not just use a dictionary, or why not just leave the type unannotated? If you really can't (or don't want to) say anything about the type, then don't. Python is dynamically typed!
Funny, I ran into the same pattern just a few months ago!
In practice, I found it difficult for coworkers to read and understand so I dropped the idea.
Another limitation I found is that it breaks down when you start using inheritance. For example:
```
class _A: pass
A = NewType("A", _A)
class _B(_A): pass
B = NewType("B", _B)
def foo(a: A) -> None: pass
b = B(_B())
foo(b) # Mypy is not happy: Argument 1 to "foo" has incompatible type "B"; expected "A"
foo(A(b)) # Mypy is OK
```
The main problem with such approach is that `class _RealShipOpts:` is very ugly to write unit tests for. You need to import a private entity in tests. I would slightly change the presented approach, and move the "public" `ShippingOptions`, `shipFast`, etc., into a new module that is a public API, for my users to use something like `from my_lib.shipping.api import ShippingOptions`.
That way, I can use "normal" naming in `class RealShipOpts:...`, and be explicit that it's not really public for the end users (they should use the `.api` module instead).
I'm sorry but if you write Python functions/methods in camel case I can't take you seriously.