I know that python normally prohibits that. but I remember that there was (and maybe still is) some obscure way to do so if you do it like in the definition of lt or something like that. unfortunately I can’t find it anymore.
It was something similar to this if I recall correctly. But this doesn’t work and I don’t remember the “correct” way anymore.
You can technically replace the int definition in globals()[‘__builtins__‘] at runtime, but changing one of the builtins like that will cause tons of issues since it’s not just your code that uses them.
I’ll start with one piece of complete certainty: Any weird hack like that cannot affect the addition of two constants. That is done at compile time, and once your evil class has done its work, the print call is identical to just print(3).
As others have mentioned, you can redefine the name int, either for this module or elsewhere. So I suppose what you might do is:
class StudiedEvilButGotAnF:
def __lt__(self, other):
global int
class int(int):
def __add__(self, other): return 42
return True
StudiedEvilButGotAnF() < 1
print(int("1") + 2)
Doesn’t really prove much, and it isn’t a bug, nor is it obscure. But I have no idea what quirk you might have been thinking of here. If you’re certain it was a bug, and that it existed in Python 3.9, I’d recommend browsing the changelogs for the 3.9 branch, seeing if anything catches your eye.
<bound method my_dunder of 'A'>
<bound method my_dunder of 1>
AB
3
Traceback (most recent call last):
File "/mnt/c/Users/janer/Documents/Projects/TicTacToe/test.py", line 24, in <module>
print("1"+2)
~~~^~
TypeError: can only concatenate str (not "int") to str
Notice how calling __add__ directly uses the override, but using + still calls the BINARY_OP operator. This is I believe, a CPython optimization since you are not allowed to override builtin methods.
The first is that if i do “1+2” the bytecode already shows 3 as Chris said and even with ways where that doesnt happen, we dont call the overridden dunder but still the builtin.
What i had remembered wasnt how to override dunders of builtins but only how to circumvent the fact that you cant modify them for adding/replacing attributes.
Ah. There’s a very VERY important subtlety here. When you do something like "a" + 1, Python does not actually call "a".__add__(1) but type("a").__add__("a", 1). Setting a __dict__ on an object will allow you to set attributes on it, but won’t allow you to redefine dunders. (There’s also other subtleties in CPython relating to slots - not to be confused with __slots__ - and, just to make things more fun, there are some special cases where putting something on an instance DOES work, but they’re rare.)
Normal attempts to overload the builtin methods directly raise exceptions since they’re implemented as descriptors.
Attempting to overwrite the __dict__ attribute will also raise exceptions because the __dict__ attribute is a mappingproxy which prevents assignments.
Those two alone are enough to prevent someone from doing this in any reasonable situation I think. The fact that you can assign an instance dict to the type at runtime and then call the method that you put in that dict is interesting, but it would be weirder to me if that didn’t work since that’s normal behavior.
For method, python will calll tp_* to operate. For static types (e.g. int, str and some python classes defined in c), the tp_* is override by their own implemention, and python will generate the callback with corresponding name as keys to __dict__ (for int it will call PyLong_Type.tp_as_number.nb_add). For heap types (e.g. classes that use keyword class to defined), python do it on the contrary. So covering __add__ to class int cannot influence PyLong_Type.tp_as_number.nb_add