You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If __new__() is invoked during object construction and it returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to the object constructor.
If __new__() does not return an instance of cls, then the new instance’s __init__() method will not be invoked.
__new__() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation.
And, indeed, packages exist that utilizes this feature to provide str-compatible custom types. However, when trying to get a "string representation" of a str-compatible instance like such with str(x), the following happens:
Since str is a type, calling into it is essentially creating a str instance (__new__ and __init__). str implemented __new__, so it is called.
According to the documentation, the return value of x.__str__ is checked to be an instance of str (or its subclasses), which is the case here.
According to the documentation quoted above, __init__ is called and the object is returned.
The problem is the return value of x.__str__ can be already initialized, or even have a __init__ signature incompatible with str. This is not an issue for str itself since str.__init__ does nothing and have a wildcard signature (according to tests I've done), but it is trivial to have a custom (and incompatible) __init__ and break things.
Proof of concept that shows __init__ was called the second time by str():
<class 'tomlkit.items.String'>
hello
Traceback (most recent call last):
File "C:\bug\poc2.py", line 16, in <module>
print(str(instance))
TypeError: __init__() missing 3 required positional arguments: '_', 'original', and 'trivia'
This behavior is introduced by commit 8ace1ab 22 years ago, released in Python 2.3 and kept to the day.
A possible solution is to check for the exact class (instead of with subclasses) in type.__call__, however I'm not sure if this behavior is compliant with the documentation. Change str.__new__ to only allow str (and not its subclasses) to be returned by __str__ could also workaround this issue, but may break even more stuffs.
Your environment
CPython versions tested on: 2.7.18, 3.9.12, 3.11.3
Operating system and architecture: Windows 10.0.19045 x64, Debian Linux 11 x64, Fedora IoT 37 x64
Bug report
According to the documentation on
object.__new__:And, indeed, packages exist that utilizes this feature to provide
str-compatible custom types. However, when trying to get a "string representation" of astr-compatible instance like such withstr(x), the following happens:stris a type, calling into it is essentially creating astrinstance (__new__and__init__).strimplemented__new__, so it is called.x.__str__is checked to be an instance ofstr(or its subclasses), which is the case here.str.__new__.type.__call__checks if this object is an instance ofstror its subclasses, which is, again, the case here.__init__is called and the object is returned.The problem is the return value of
x.__str__can be already initialized, or even have a__init__signature incompatible withstr. This is not an issue forstritself sincestr.__init__does nothing and have a wildcard signature (according to tests I've done), but it is trivial to have a custom (and incompatible)__init__and break things.Proof of concept that shows
__init__was called the second time bystr():Sample output on Python 3.9:
A real-world example that breaks
tomlkit:Sample output:
This behavior is introduced by commit 8ace1ab 22 years ago, released in Python 2.3 and kept to the day.
A possible solution is to check for the exact class (instead of with subclasses) in
type.__call__, however I'm not sure if this behavior is compliant with the documentation. Changestr.__new__to only allowstr(and not its subclasses) to be returned by__str__could also workaround this issue, but may break even more stuffs.Your environment
Linked PRs
str(x)a str,bytes(x)a bytes #104247__bytes__and__str__when returning strict subclass #108814