
On Tue, 20 Dec 2022 at 13:56, Brendan Barnwell <brenbarn@brenbarn.net> wrote:
On 2022-12-19 13:59, Chris Angelico wrote:
On Tue, 20 Dec 2022 at 07:13, Brendan Barnwell <brenbarn@brenbarn.net> wrote:
See my example regarding a StrEnum and tell me whether that would be more irritating.
I can't run that example myself as I don't have Python 3.11 set up.
The enum module was added in Python 3.4.
Your example used StrEnum, which was added in Python 3.11.
Oh! My apologies. The older way of spelling it multiple inheritance but comes to the same thing; it's still very definitely a string. StrEnum is a lot more convenient, and I've been using 3.11 for long enough that I forgot when it came in. Even back in 3.5 (the oldest docs that I have handy), the notion of enum MI was listed as a recommended method: https://docs.python.org/3.5/library/enum.html#others
class Demo(str, Enum): ... x = "eggs" ... m = "ham"
Other than that change to the signature, the demonstration behaves exactly the same (I just tested it on 3.5). Again, my apologies for unintentionally providing an example that works only on very new Pythons.
Nonetheless, a StrEnum is absolutely a str, and whatever you say about an HTML string has to also be valid for a StrEnum, or else the inverse is.
No, it doesn't, because HTMLString and StrEnum can be different subclasses of str with different behavior. You seem to be missing the concept of subclasses here. Yes, a StrEnum may be an instance of str, and an HTMLString may also be an instance of str. But that does not mean the behavior of both needs to be same. They are instances of *different subclasses* of str and can have *different behavior*. An instance of collections.Counter is an instance of dict and so is an instance of collections.defaultdict, but that doesn't mean that anything I say about a Counter has to be valid for a defaultdict.
That is very true, but whenever the subclass is NOT the same as the superclass, you provide functionality to do so. Otherwise, the normal assumption should be that it behaves identically. For instance, if you iterate over a Counter, you would expect to get all of the keys in it; it's true that you can subscript it with any value and get back a zero, but the default behaviour of Counter iteration is to do the same thing that a dict would. And that's what we generally see. A StrEnum is a str, and any behaviours that aren't the same as str are provided by StrEnum (for instance, it has a different __repr__). But for anything that isn't overridden - including any new functionality, if you upgrade Python and keep the same StrEnum code - you get the superclass's behaviour.
The way things are, a StrEnum or an HTML string will behave *exactly as a string does*. The alternative is that, if any new operations are added to strings in the future, they have to be explicitly blocked by StrEnum or else they will randomly and mysteriously misbehave - or, at very best, crash with unexpected errors. Which one is more hostile to subclasses?
I already answered that in my previous post. To repeat: StrEnum is the unusual case and I am fine with it being more difficult to create something like StrEnum, because that is not as important as making it easy to create classes that *do* return an instance of themselves (i.e., an instance of the same type as "self") from their various methods.
I'm of the opinion that this is a lot less special than you might think, since there are quite a lot of these sorts of special cases.
The current behavior is more hostile to subclasses because people typically write subclasses to *extend* the behavior of superclasses, and that is hindered if you have to override every superclass method just to make it do the same thing but return the result wrapped in the new subclass.
Maybe, but I would say that the solution is to make an easier way to make a subclass that automatically does those changes - not to make this the behaviour of all classes, everywhere. Your idea to:
One way that some libraries implement this for their own classes is to have an attribute or method called something like `_class` or `_constructor` that specifies which class to use to construct a new instance when needed. By default such a class may return an instance of the same type as self (i.e., the most specific subclass), but subclasses could override it to do something else.
... have a _class attribute may be a good way to do this, since - unless otherwise overridden - it would remain where it is. (Though, minor bikeshedding - a dunder name is probably more appropriate here.) It could even be done with a mixin: class Str(autospecialize, str): __autospecialize__ = __class__ def some_method(self): ... and then the autospecialize class can handle this. There are many ways of handling this, and IMO the best *default* is to not do this. ChrisA