Python int
s are objects that encapsulate the actual number value. Can we mess with that value, for example setting the value of the object 1
to 2? So that 1 == 2
becomes True
?

- 30,738
- 21
- 105
- 131

- 23,480
- 7
- 29
- 65
-
32Reminds me of a story about a version of Fortran where. if you passed a literal `1` to a procedure, it could set the constant `1` equal to `-1` through that reference, and thanks to the compiler merging all references to the same literal, it would then make every loop in the program run backwards. – Davislor Jan 28 '22 at 05:04
-
2@Davislor Passing a literal 1 sounds like a normal thing to do, and modifying an argument does as well. So, was that not even a conscious hack like mine, but could actually happen unintentionally by accident? Scary... – Kelly Bundy Jan 28 '22 at 09:30
-
4Does this answer your question? [Clarification for "it should be possible to change the value of 1" from the CPython documentation](https://stackoverflow.com/questions/62188158/clarification-for-it-should-be-possible-to-change-the-value-of-1-from-the-cpyt) or [Can 1 + 1 be equal to 3 in python?](https://stackoverflow.com/q/53950215/1048572) – Bergi Jan 28 '22 at 12:45
-
I tried to play a bit with https://github.com/clarete/forbiddenfruit but didn't manage to redefine int#__eq__ – Eric Duminil Jan 28 '22 at 13:28
-
Python's reference implementation (CPython) exposes an API for library developers and during my work on some C++ lib I was surprised how much stuff you can fiddle with from the inside. It basically exposes all type information about implementation and nothing stops you from interfering even with crucial elements like reference counts and memory addresses of specific (sub)objects. It feels just as if you were editing interpreter's code. – Xeverous Jan 28 '22 at 14:08
-
1There is a similar question with different languages in the codegolf.SE: https://codegolf.stackexchange.com/questions/28786/write-a-program-that-makes-2-2-5 You will find some python answers there as well. – izlin Jan 28 '22 at 14:30
-
@KellyBundy: Where integers are defined, in `that_would_be_nice/int.py`. – Eric Duminil Jan 28 '22 at 20:24
-
2Ace left-handed programmer, James Marshall Hendrix, evolved a coding style that was immune to this defect: "Now if six turned out to be nine, I don't mind, I don't mind". [Link.](https://www.azlyrics.com/lyrics/jimihendrix/if6was9.html) – passer-by Jan 28 '22 at 22:38
-
In Javascript, one can make Math.PI = 2 (but later when you call Math.PI it loads again the default 3.14 ). The code is written as `console.log(Math.PI = 2); // 2` then `console.log(Math.PI); // 3.141592653589793` and can be understood as Math.PI extracting the value of property PI of the built-in object Math and assigning it with value of 2 (for a single instance only) then again extracting the original value of PI when executing the second line of code. – Eve Jan 29 '22 at 14:08
-
@Eric Duminil Yes, already read and upvoted it yesterday. If I ever want to do something like this again, I'll probably use the method shown there, as in general it's more convenient (although I still prefer `memmove` for my case here). – Kelly Bundy Jan 30 '22 at 08:12
3 Answers
Yes, we can. But don't do this at home. Seriously, the 1
object is used in many places and I have no clue what this might break and what that might do to your computer. I reject all responsibility. But I found it interesting to learn about these things.
The id
function gives us the memory address and the ctypes
module lets us mess with memory:
import ctypes
ctypes.memmove(id(1) + 24, id(2) + 24, 4)
print(1 == 2)
x = 40
print(x + 1)
Output:
True
42
Try it online!. I tried it there because such sites have got to be protected from our hacking anyway.
More explanation / analysis:
The memmove
copied the value from the 2
object into the 1
object. Their size is 28 bytes each, but I skipped the first 24 bytes, because that's the object's reference count, type address, and value size, as we can view/verify as well:
import ctypes, struct, sys
x = 1
data = ctypes.string_at(id(x), 28)
ref_count, type_address, number_of_digits, lowest_digit = \
struct.unpack('qqqi', data)
print('reference count: ', ref_count, sys.getrefcount(x))
print('type address: ', type_address, id(type(x)))
print('number of digits:', number_of_digits, -(-x.bit_length() // 30))
print('lowest digit: ', lowest_digit, x % 2**30)
Output (Try it online!):
reference count: 135 138
type address: 140259718753696 140259718753696
number of digits: 1 1
lowest digit: 1 1
The reference count gets increased by the getrefcount
call, but I don't know why by 3. Anyway, ~134 things other than us reference the 1
object, and we're potentially messing all of them up, so... really don't try this at home.
The "digits" refer to how CPython stores int
s as digits in base 230. For example, x = 2 ** 3000
has 101 such digits. Output for x = 123 ** 456
for a better test:
reference count: 1 2
type address: 140078560107936 140078560107936
number of digits: 106 106
lowest digit: 970169057 970169057

- 30,738
- 21
- 105
- 131

- 23,480
- 7
- 29
- 65
-
Quick question, what is the meaning of the "21 .__sizeof__() - 24" argument? (I see it is changed to 4 instead now, it is the size?) – Richard K Yu Jan 27 '22 at 17:02
-
6@RichardKYu I had originally used `1 + 1 == 42` as the target example, so had changed the value of `1` to 21 for that. Then when I switched to `1 == 2` as target example, I forgot to change `21` to `2` there in the code. – Kelly Bundy Jan 27 '22 at 17:03
-
@ilkkachu I guess so. Better at home than at work, but still better to just try it on those online services with professionally sandboxed environments. – Kelly Bundy Jan 28 '22 at 09:23
-
2Obviously this works *only* in CPython. I'm pretty sure the Python language doesn't give any semantics to the value returned by `id` and this answer shows why using the memory address is probably not the best option... sure itis the simplest & fastest probably – GACy20 Jan 28 '22 at 10:39
-
8For me this crashed the interpreter. Trying it with some more rarely used vales (17 and 18) did work! – Todd Sewell Jan 28 '22 at 11:02
-
1@GACy20 Well, the question *is* tagged as being specific to CPython :-). I don't think it's obvious, though. That would require knowing it for every other implementation. How many people do? I certainly don't, I can't even *name* all of them. In any case, it doesn't really matter... like I said, this was just out of curiosity / for fun. I don't intend to actually *use* this for anything. I'm interested in how CPython works, so this was educational for me. – Kelly Bundy Jan 28 '22 at 11:45
-
@Todd Sewell You daredevils... I wouldn't even try *that* on my PC. I considered trying it with a larger int after creating it myself and verifying that I have the only reference, but really I'm satisfied with doing it online. – Kelly Bundy Jan 28 '22 at 11:50
-
-
@Mezgrman: Did you try it in the python console? It segfaulted on mine. But it worked fine when running `python dont_do_this_at_home.py`. – Eric Duminil Jan 28 '22 at 13:00
-
1@Eric Duminil: Yeah, both actually. It doesn't segfault, it just prints False and 41, respectively. (Python 3.7.4) – Mezgrman Jan 28 '22 at 13:02
-
1`print(list(range(10)))` #=> `[0, 2, 4, 6, 8]`. LOL. And by the way, there's a related Reddit thread with very similar code : https://www.reddit.com/r/Python/comments/2441cv/can_you_change_the_value_of_1/ – Eric Duminil Jan 28 '22 at 13:08
-
1@KellyBundy There's really not that much risk, the worst that can happen is some kind of segfault, which is not that rare when developing C/C++ software anyway. Processes are isolated by the OS, it will never end up taking down the entire PC. – Todd Sewell Jan 28 '22 at 13:14
-
1@Eric Duminil Oh wow .. `range` did cross my mind but I didn't test it because I thought it's surely optimized for "normal" ranges to internally work with C integers and only produce Python ints when it has to produce them. Now I'm even more worried than before. And I shall look into what `range` really does. – Kelly Bundy Jan 28 '22 at 13:32
-
@Todd Sewell I'm not worried about directly harming other processes, but about files. Either directly my own files, or some Python/system/program files, which could cause trouble later. – Kelly Bundy Jan 28 '22 at 13:38
-
2@Eric Duminil Ah, `range_iterator` indeed has that optimization, but it's too late! The `range` object uses the Python `1` as `step`, and then the iterator reads it's value 2. And here I was, thinking lack of that optimization might be another reason why [`range` is so slow](https://tio.run/##RY5LDoMwDET3OYWXAbEAsUFIXKFXqPiYNlJip66R6OlTKLTdeeaNRxNfemeqmygpzcIB1AV0Ci5EFj2V@RCnKMrsn18oGLE/4cje46iO6YcnfCxoDEEHVZnnjZlZ4AqOQHq6oa2z1sDurX@PsuKstRcmLID2EERxpPYYY30fhqlvj367FlBuT7SEAaWrtnPNUnoD). – Kelly Bundy Jan 28 '22 at 14:03
-
@KellyBundy Thank you for finding small mistakes in my stackoverflow answers. [Here's a tribute](https://github.com/bharel/4to5). – Bharel May 20 '22 at 17:37
-
1
-
In Python 2, there's a much simpler approach - True
and False
aren't protected, so you could assign things to them.
>>> True = False
>>> (1 == 2) is True
True

- 30,738
- 21
- 105
- 131

- 6,412
- 1
- 26
- 33
-
6Doesn't do it the asked-about way, though. Maybe I should've kept how I phrased it in the title. Although then according to [Python 2's definition of true/false](https://docs.python.org/2.7/library/stdtypes.html#truth-value-testing), I think you could *switch* the values of `True` and `False` and still argue to have achieved the desired result (although still not in the requested way). Anyway, +1 from me because it at least does achieve the `(1 == 2) is True` and is interesting as well. – Kelly Bundy Jan 28 '22 at 12:16
-
9If `True = False` then shouldn't `False is True` be `False` (since it's `True`)? – BlueRaja - Danny Pflughoeft Jan 28 '22 at 21:50
-
1@BlueRaja-DannyPflughoeft no, this value is distinct (resides in a different address of RAM). It's equal to the original value of `True` and compares negatively to both `True` and `False` after the reassignment. – Ruslan Jan 29 '22 at 13:57
-
2Just some meta-thoughts: The origin of my question is that someone proposed a solution to another task where they intended to use O(1) extra memory by "marking" input numbers by negating them. I then pointed out that in Python, the negated numbers are new objects, so they took O(n) extra memory. That made me wonder whether we can *modify* the given int objects instead to actually achieve O(1). So what I meant was "make 1 == 2" as in "*make 1* equal 2" as in "modify 1 so it equals 2". But now I'm glad I didn't express that well, as I and many others appreciate your answer to what I did express. – Kelly Bundy Jan 29 '22 at 14:01
If you'd prefer not to mess with the actual contents of cached int
or bool
objects, you can fake making 1 == 2
like so:
>>> import builtins
>>> import sys
>>>
>>> def displayhook(value):
... if value is False:
... value = True
... elif value is 1:
... value = 2
... text = repr(value)
... sys.stdout.write(text)
... sys.stdout.write('\n')
... builtins._ = value
...
<stdin>:4: SyntaxWarning: "is" with a literal. Did you mean "=="?
>>> sys.displayhook = displayhook
>>> 1
2
>>> 1 == 2
True

- 87,747
- 23
- 163
- 198
-
1
-
2For people like me, that don't know python too well, it is could be useful to know that this is an interactive interpreter session, thus [`sys.displayhook()`](https://docs.python.org/3.8/library/sys.html#sys.displayhook) can be used to modify the output: "this function prints repr(value) to sys.stdout". Another thing I didn't know: `_` refers to the result of the last executed statement in interactive sessions. – void Feb 03 '22 at 06:52