Swap_8_and_9: A simple import can modify the Python interpreter
kenschutte.com
kenschutte.com
https://codegolf.stackexchange.com/questions/28786/write-a-p...
Really shows you the skeletons hiding in some languages. My favorite is Haskell, which will happily do what you tell it to.
λ> let 2+2=5 in 2+2
5
This simply defines a new local function that happens to be called "+". It's no different than something like this in JavaScript: let Math = { sqrt: (x) => 42 };
console.log(Math.sqrt(16));https://youtube.com/watch?v=F3feRCr6S64
(recommend watching it by doing the four "tests" before going to the reveal)
: 4 5 ;
2 2 + .What you did will make inputting a 4 yield a 5, but it won't change the behaviour of outputting a 4. (And it will only turn an input 4 into a 5 if your interpreter checks for words before checking for numbers which is not universally the case).
ctypes.c_int.from_address(id(8)+24).value = 9 ctypes.sizeof(ctypes.c_long) * 3Even in C itself, you can do the same in shared libraries. It's a very important functionality that the library can run some init code when it is being loaded. And you can do all kinds of strange things, modifying some random memory.
Though with the specializing compiler work, some stuff is... less obvious. But I still generally find it straightforward when I want to know some detail about how the language works.
import antigravity
As others have mentioned, every line of code not in a function/class gets executed when imported, except if guarded with an `if __name__ == '__main__':` (only true when executing the script with `python xxx.py`). A related catch: functions' default arguments are also evaluated when imported the first time.Try creating `fun.py`:
def evil():
print("Gotcha")
return 1
def abc(x = evil()):
print(x)
Now: `python fun.py` or `python -c "import fun"`It uses implementation-specific details which are outside of the scope of anything to do with Python semantics.
It's roughly equivalent to:
#include <stdio.h>
int nine = 9;
int main(void) {
printf("nine = %d\n", nine);
return 0;
}
/* in a library */
__attribute__((constructor))
static void sneaky(void) {
int *n = (int *) &nine;
*n = 8;
}
Your hyperbole simply isn't true, as demonstrated by the many static code analysis tools for Python. They can't handle all cases, certainly, but they demonstrate it's mostly possible to give semantics for Python code without running it.You don't have to be "sneaky" for this to bite you with Python. Maybe it looks obvious when stated in a bare-bones fashion but this bug was not easy to track down in a larger code base:
i = 1
incr_by_1 = lambda x : x + i
i = 4
incr_by_4 = lambda x : x + i
i is a reference in both incr_by_1 and incr_by_4 are equivalent at this point. If anyone assigns to i, then their behaviour will change.In most languages, integers are values so an optimiser has a chance to (for example) replace incr1 by a single CPU increment instruction but can't do it here as the value "pointed to" by i needs to be fetched according to Python semantics.
To be fair, the equivalent in C++ is:
#include <iostream>
int main() {
int i;
i = 1;
auto incr_by_1 = [&](int x) {return x + i;};
i = 4;
auto incr_by_4 = [&](int x) {return x + i;};
std::cout << incr_by_1(0) << " and " << incr_by_4(0) << std::endl;
return 0;
}
which prints "4 and 4". Replace the first [&] with [i] and it prints "1 and 4".A Python implementation also can't replace incr1 by a single CPU increment instruction because it doesn't know the type of x.
That's still a far cry from being unable to give semantics for Python code without running it.
Fortran had basically the same bug, fifty years ago. https://softwareengineering.stackexchange.com/a/254921
In this case, setuptools is a drop in replacement.
When you import a module, Python executes it. For instance, `def` isn’t syntax that says “hey compiler, this is a function!” It’s a statement that’s executed at runtime to define a function. You can put any code you want at the top level of a module and it’ll get executed when the Python interpreter gets to that line.
This is why people use the `if __name__ == "__main__"` so the majority of people will address it in all their scripts even if they don't know the reason why.
It's a feature not a bug IMO. You can use importing a .py file as a singleton hack. You can also use `refresh` to re-load a module, to clear it of any runtime overrides.
`source` is much more primitive.
C extensions don't significantly change matters because the module is still constructed by procedural C code.
: REAL8 8 ;
: 8 9 ;
: 9 REAL8 ;The author could have skipped the c library and used the ctypes module to munge the bits.
There's no guarantee that any other version of python would use the same caching, same structure layout and certain not be able to link with the same c library.
So, yeah, it's specific.
A fun little adventure into how things work for the author, though.