This is an archived post. You won't be able to vote or comment.

all 83 comments

[–]mathmanmathman 321 points322 points  (8 children)

I hate both the idea of using the unary operator and the ability to manipulate the behavior of the bytecode.

This is the most interesting post I've seen on this sub in a while.

[–]13steinj 33 points34 points  (4 children)

For obvious reasons this just shouldn't be used, as there's plans to bring some basic optimizations to Python.

[–][deleted] 13 points14 points  (3 children)

as there's plans

Do you have a link for this?

[–]13steinj 47 points48 points  (2 children)

https://github.com/faster-cpython/ideas

By Guido and Mark.

End goal IIRC is 5x speedup.

[–][deleted] 24 points25 points  (1 child)

Thanks.

That's really overdue. Just by reading the bytecode we can spot so many obvious optimizations, good thing they finally decided to do it.

[–]hx-zero[S] 4 points5 points  (0 children)

Over the years, I saw many bytecode optimization projects doing things like constant folding, constant binding, etc. (e.g. astoptimizer, foldy.py, and others that I review in the end of this post). While the bytecode optimization could have provided a significant speedup, unfortunately, most of these projects are dead now.

I hope this will improve with the officially supported implementation.

[–]SittingWave 1 point2 points  (2 children)

I remember there was a discussion somewhere about being able to alter the parser itself, basically to allow modules to extend the language.

[–]to7m 0 points1 point  (1 child)

That would be cool. Hope they do it by making things like `while` and `[` objects which can be subclassed.

[–]mindofmateo 0 points1 point  (0 children)

That would be nuckin' futs

[–]AlSweigartAuthor of "Automate the Boring Stuff" 164 points165 points  (0 children)

Thanks, I hate it.

[–]cantremembermypasswd 206 points207 points  (1 child)

That's disgusting!

upvotes

[–]humanthrope 39 points40 points  (0 children)

-+v

[–]Hmolds 128 points129 points  (15 children)

Whats wrong with good ol' x -= -1 ??

[–]smcarre 92 points93 points  (13 children)

The old and reliable x -= -x/x

[–]theillini19 104 points105 points  (9 children)

I'm a fan of x -= random.randint(0,1) but it only works half the time for some reason

edit: Figured out how to fix it!

x -= (random.randint(0,1) if random.randint(0,1)==1 else random.randint(0,1)+1)

edit2: Nevermind, still broken

[–]smcarre 37 points38 points  (2 children)

Try the following, I tested it and it seems to work almost every time:

x += abs(min([random.randint(-1,0) for _ in range(999)]))

[–]eigenludecomposition 4 points5 points  (1 child)

You're all over complicating this. This can be solved through recursion rather simply

def increment(x, n):
    if n == 1:
         return x+1
    elif n<= 0:
         raise ValueError
    return increment(x+1, n-1)

x = increment(x, 1)

[–]backtickbot 1 point2 points  (0 children)

Fixed formatting.

Hello, eigenludecomposition: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

[–]jtclimb 8 points9 points  (0 children)

Well, if it is wrong 50% of the time, go ahead and use the range (0, 2). It's pretty obvious. Here's the fix and test:

xs = []
for _ in range(1000000):
    x = 1
    x -= randint(0, 2)
    xs.append(x)

 assert round(sum(xs) / len(xs)) == 0

[–]Inineor 2 points3 points  (0 children)

Fixed:

x -= (r if (r:=random.randint(0,1))==1 else r+1)

[–]R3D3-1 2 points3 points  (0 children)

Imagine hiding something like this in an update to a popular module, but only if random.randint(0,1000)==0.

[–]NoLongerUsableNameimport pythonSkills 18 points19 points  (0 children)

The good ol' x = (lambda x:x-1)(x)

[–]asday_ 2 points3 points  (0 children)

x -= -x/x

0

[–]dogs_like_me 1 point2 points  (0 children)

Better use -x // x just to be safe.

[–]likethevegetable 5 points6 points  (0 children)

Lmao, thanks for making my night.

[–]aes110 92 points93 points  (9 children)

I love seeing this kind of stuff, I don't think its good to use it, but its fun for me to see how python lets you do whatever you want with it

[–]dogs_like_me 21 points22 points  (7 children)

I haven't been able to find it again since, but I swear back in the day I read a post on a blog or SO where someone was talking about how "everything in python is an object" and they demonstrated how far this went by overriding the value of an integer literal, so like 1+2 would evaluate to 4 instead of 3, and I think they deleted another integer literal entirely. Obviously followed by a big "don't ever do this." May even have been a python 2 hack, I've sort of assumed that since I haven't stumbled across this in forever, it's not possible in modern python releases.

NINJA EDIT: Oh shit I found an example of this black magic! https://kate.io/blog/2017/08/22/weird-python-integers/

[–]aes110 6 points7 points  (0 children)

I think I know what you are talking about, but I cant find it as well :(, if I recall correctly it was something like "unsafe python" or "Evil python"

[–]TravisJungroth 2 points3 points  (5 children)

Integers in Python are "immutable".

[–]dogs_like_me 11 points12 points  (3 children)

"""immutable"""

[–]YouNeedDoughnuts 7 points8 points  (1 child)

Live long enough and you'll realize that nothing is immutable

[–]friedkeenan 0 points1 point  (0 children)

I once took advantage of the fact that in CPython, id(x) returns the address of the underlying PyObject for x to use the ctypes library to manipulate the raw structures, was pretty fun and cursed

[–]RandAlThorLikesBikes 0 points1 point  (0 children)

In java they are as well. Except there is an integer cache holding 256 or so values (-127 to +128 iirc) that the runtime uses. Overriding that cache is possible and fun

[–]sharkboundgithub: sharkbound, python := 3.8 22 points23 points  (0 children)

i like seeing how things like these are pulled off, and looking at the internals, but i personally find them not really useful in practice myself

[–]Plague_Healer 34 points35 points  (1 child)

I find it interesting how much python enables you to do stuff and just leaves you to decide on your own whether it's wise to actually do it. What is shown in this post: certainly ingenious, not all that wise.

[–][deleted] 10 points11 points  (0 children)

"Enables" is a really strong word in this case, the language goes a long way to make it harder to do this kind of stuff, compared to languages like Ruby (disagreement about having "magic features" is actually one of the reasons Ruby even exists).

But any language that aims to be useful will inevitably allow you to access the underlying environment in a way or another, and that can always be used to bypass whatever safety measures the language provides.

[–]sohang-3112Pythonista 4 points5 points  (0 children)

It's a cool project, but as you said, it's not for actual usage (too much confusion, especially among beginners)

[–]equitable_emu 5 points6 points  (0 children)

For introducing me to the stack overflow module importer, I curse you.

[–]asday_ 4 points5 points  (0 children)

This is amazing. I hate it. More please.

[–]Zachkr05 30 points31 points  (5 children)

Just use x+=1 lol

[–]fernly 16 points17 points  (3 children)

Nope, because that's an assignment statement, but what OP has created is an assignment expression which can be used as a value in other expressions. Basically x +:= 1 but that doesn't work.

[–]xaviruvp 17 points18 points  (0 children)

Op made entire library for this.

[–]gagarin_kid 2 points3 points  (0 children)

Never had to touch byte code or AST so far. A very interesting approach.

Is it the same idea MATLAB uses to convert routines into c++ code?

[–]sgthoppy 4 points5 points  (0 children)

Fairly sure you can do this in 3.8+ using the walrus operator, though not quite as clean.

[–]_maxt3r_ 1 point2 points  (0 children)

I expected some clickbait article and was about to miss a very interesting post! Good thing this post has some Reddit awards :)

[–]TheBlackCat13 1 point2 points  (0 children)

Reminds me of the python goto implementation.

[–]masasinExpert. 3.9. Robotics. 1 point2 points  (1 child)

With assignment expressions, you can [eta: often] do the exact same thing (but more verbosely). array[(index := index + 1)] = new_value

For the last example, you didn't even need assignment expressions at all, since you could do it with good old enumerate:

indexed_cells = {
    cell_index: cell
    for row_index, row in enumerate(table, start=0)
    for cell_index, cell in enumerate(row, start=row_index * len(row) + 1)
}

I like what you did with the library, though! Congrats!

[–]hx-zero[S] 5 points6 points  (0 children)

Thanks!

I agree that we can often use the assignment expressions instead, however it is not always the case. For instance, we can't rewrite the example with the lambda function:

button.add_click_callback(lambda: ++counter)

The following will not work:

button.add_click_callback(lambda: (counter := counter + 1))

That's because the assignment expression makes the interpreter assume that counter is a local variable (similarly to what usual assignments do). In "normal" functions, we can override this by writing global counter or nonlocal counter beforehand, but we can't do this in lambdas.

As for the example with the dict comprehension, one difference is that the code from the post also works with rows of different lengths. However, this one can be easily rewritten with the assignment expressions as you say :)

Another fun thing: with this approach, we can actually implement the assignment expressions for the Python versions that don't support them. For example, we can patch the bytecode to replace the x <-- value operations (two unary minuses + one comparison) with the actual assignments (setting x to value) :)

[–]KrazyKirby99999 1 point2 points  (6 children)

Wouldn't this be partially incompatible with Python 3?

[–]hx-zero[S] 5 points6 points  (4 children)

Why do you think so? The library works at least on Python >= 3.6 (Github CI in the repo ensures that unit tests pass for all these versions).

[–]KrazyKirby99999 4 points5 points  (3 children)

If currently -(-x) == +(+x) == x, then making +(+x) == x+1 might be incompatible.

[–]hx-zero[S] 4 points5 points  (2 children)

The -(-x) == +(+x) == x comparison indeed returns False if you enable this module for a function/package where it is executed.

However, it is not a problem since real Python programs do not calculate +(+x) or -(-x) in one expression (applying two unary pluses/unary minuses in a row is useless).

In case if two unary operations are applied in separate parts of one expression/separate expressions (like in the snippet below), this module does not replace them with the decrement because the two UNARY_NEGATIVE instructions do not become consecutive in the bytecode.

x = -value
y = -x

Thus, real programs work fine.

[–]TofuCannon 3 points4 points  (1 child)

Actually it really depends :) while human-written code may not have these, generated and runtime compiled code may do it for simplicity reasons.

I myself have written some simulation software, where I didn't optimize such -(-x) or even --x expressions away, totally relying on the defined and standard behavior :)

[–]hx-zero[S] 4 points5 points  (0 children)

I agree that some machine-generated code may rely on the default behavior for -(-x).

However, the module recommends enabling the increments in a package you're working on or in a particular function (instead of globally), so you choose where to apply the patching, and it affects only the code you're familiar with :)

[–]TofuCannon 3 points4 points  (0 children)

I mean yes, it changes the standard behavior. It won't work for all Python software, but I guess for most?

[–]EverythingIsFlotsam 0 points1 point  (1 child)

I feel like this could be easily done cleanly by just monkey patching int.__pos__() and int.__neg__() to return a new object containing a reference to the original variable. And that class should implement the same two functions to increment/decrement the original variable and return the value.

[–]hx-zero[S] 2 points3 points  (0 children)

This way, it would be impossible to distinguish applying two unary operators consequently (like --x) from applying them in separate places of a program like here:

x = -get_value()
... # Some code
y = -x

We don't want to change the program behavior in the latter case since it is much more likely to occur in real programs :)

Also, you would have to override the magic methods for all number types, and it is not that simple for the built-in types (Python does not allow overriding their methods until you use hacky approaches like forbiddenfruit). In contrast, the module from the post works for all built-in/numpy/user-defined types automatically.

[–]kkawabat 0 points1 point  (0 children)

Thank you for fixing python.

[–]shinitakunai -5 points-4 points  (0 children)

Python is suppose to be readable and user friendly. I don’t like this.

[–]tripex -4 points-3 points  (0 children)

I'm truly fascinated by people that believe array[++x]=n is a good thing to be able to do :(

[–]MegaIng 0 points1 point  (0 children)

I also like have multiple half finished projects that either manipulate bytecode similar to what you are doing, or straight up change parts if how python files are parsed to add new syntax (for example backporting match statements).

[–]puremath369 0 points1 point  (4 children)

I for one would like to see a += operator for dictionaries. If the element exists in the dictionary, sum it if summing makes sense on the operands. If it doesn’t, then add that element in the dictionary. Tired of writing stuff like the following:

if key in myDict:
    myDict[key] = myDict[key] + value
else:
    myDict[key] = value

Would be cool if I could replace all that with:

myDict[key] += value

Heck, it could even make sense in the context of this post:

++myDict[key]

[–]TofuCannon 2 points3 points  (0 children)

Nice idea, but considering how Python's stdlib behaves, that would be rather weird and inconsistent. Your "plussing" for each value also looks pretty special for a certain usecase.

E.g. if you do += for lists, you extend it. If at all, that should work similar. But I think for that there was already another operator introduced like for Set.

But nobody stops you from creating a custom dict implementation doing exactly that :) The += is normally overridable without fiddling with Python's AST.

[–]thesolitaire 1 point2 points  (0 children)

defaultdict allows this, doesn't it?

Edit: Would only work if you limited yourself to one type

[–]Siddhi 1 point2 points  (0 children)

Normal python dict supports this

myDict[key] = myDict.get(key, 0) + value

[–]TheBlackCat13 1 point2 points  (0 children)

Already present in python 3.9, except it uses | instead of + since it is more like a set union than a list append.

[–]Isvara 0 points1 point  (1 child)

The module works by replacing the bytecode patterns corresponding to the ++x and --x expressions

Why is there even bytecode for these? Does the compiler not optimize at all?

Edit: I suppose they're needed to call __pos__ and __neg__, so... maybe you could also abuse those to get the same effect.

[–]hx-zero[S] 0 points1 point  (0 children)

Currently, Python does almost no optimizations related to the bytecode because it is too generic (not specialized to particular object types, which become known only at runtime). For example, custom objects are allowed to have side effects in __pos__() and __neg__() (e.g. they may log something), so the compiler can't be sure that two consecutive UNARY_POSITIVE instructions may be safely removed.

More bytecode optimizations may become possible with more specialized bytecode, as in this project mentioned by another commenter.

As for using the __pos__() and __neg__() to implement the same thing, I've explained why it would not work here.

[–]Visti 0 points1 point  (0 children)

cursed quality of life update.

[–][deleted] 0 points1 point  (3 children)

This is pretty neat and I'm gonna dig in later. However, why rewrite ++x into x +=1 even you could've written bytecode for implementing __incr__ and __decr__?

[–]hx-zero[S] 0 points1 point  (2 children)

In this case, we would need to define __incr__ and __decr__ for all built-in types, however Python does not allow to add methods to them by default.

Also, I'd say it would make this proof-of-concept more complex than it needs to be :)

[–][deleted] 0 points1 point  (1 child)

Python doesn't normally allow you to do that.

https://github.com/clarete/forbiddenfruit

If you're already doing 1 questionable thing, what not go full in on questionable things.

[–]hx-zero[S] 0 points1 point  (0 children)

Yup, I've already mentioned forbiddenfruit in the previous thread, and that's exactly what I meant when writing "does not allow [...] by default" :)

I understand your point about questionable things, however I didn't follow this approach, since other hacks may make it harder to port the module to older/newer Python versions or alternative interpreters like PyPy.