The "walrus operator" will occasionally be useful, but I doubt I will find many effective uses for it. Same with the forced positional/keyword arguments and the "self-documenting" f-string expressions. Even when they have a use, it's usually just to save one line of code or a few extra characters.
The labeled breaks and continues proposed in PEP-3136 [0] also wouldn't be used very frequently, but they would at least eliminate multiple lines of code and reduce complexity.
PEP-3136 was rejected because "code so complicated to require this feature is very rare". I can understand a stance like that. Over complicating a language with rarely-used features can definitely create problems. I just don't see why the three "headline" features I mentioned are any different.
The primary one I want is
if m := re.match(...):
print(m.group(1))
and while s := network_service.read():
process(s)
both of which are both clearer and less error-prone than their non-walrus variants.The other one that I would have found useful an hour ago is in interactive exploration with comprehensions. I frequently take a look at some data with [i for i in data if i[something].something...], and being able to quickly give a name to something in my conditional, as in {i: x for i in data if x := i[something]}, helps maintain the focus on data and not syntax. Obviously it will get rewritten to have clearer variable names, at the least, when it gets committed as real code, and almost certainly rewritten to be a normal non-comprehension for loop.
Like comprehensions, I expect the walrus operator to be valuable when used occasionally, and annoying if overused. There's no real language-level solution to bad taste. In Python 3 you can now do [print(i) for i in...], and I occasionally do at the REPL, but you shouldn't do it in real code and that's not an argument against the language supporting comprehensions.
m = re.match(...)
if m is not None:
pass
Now, I wonder what you meant saying that single-line version is less error-prone, because I don't think so. I believe they're exactly the same in this regard, except for a bizarre case when someone would bastardize the code by putting some irrelevant lines between the assignment and the comparison, obfuscating the logic.Also, as I tend to use `if m is not None:` rather than just `if m:` (because I typically don't want to run all that subtle `__nonzero__` magic), it would - subjectively - look less pretty in the one-line version: `if (m := re.match(...)) is not None:`.
Use in the generator expressions to create aliases/shortcuts and avoid repetition is great, though. I love this use case.
re.match() should always return a match object, but instead .group(1) will return None. Then we can write one-liners easier without the walrus operator.
The inside of the for-braces allow for keyword-based sub-clauses: There's
- ":when" which will suppress output for elements not matching a filter expression,
- ":while" which will stop when elements stop matching a filter, and
- ":let" which will let you bind some new values in mid-loop.
I rarely need these features, but when I do I find them really really helpful. Maybe someday someone will consider doing something similar in Python.
for s in iter(network_service.read, ''):
process(s)... I've got no idea how the syntax could look though.
That was the reasoning given for using ":=" instead of "as", to allow more complexity. I still think it was a mistake.
Watch out with this one though. In this case it's good, but if you receive numbers from such function, it will break loop not just on None but on 0 as well. Easy to forget.
In many cases you'll need to do:
while (s:= ....) is not None:https://www.python.org/dev/peps/pep-0572/#differences-betwee...
https://www.python.org/dev/peps/pep-0572/#relative-precedenc...
Even examples of the spec shows how unintuitive and "unpythonic" this is. Explicit is better than implicit.
IMHO adding features to the language to save 1 line of code for 10% of cases when you need it (I agree that there's occasional case when walrus will save you more than 1 line) is just bloat.
I am not a big proponent of Go, because it has its own flaws, though language is indeed very simple and creators of the language try to leave it simple.
IMO Python was very readable, super simple, intuitive and should stay that way, though recent releases show that Python is giving in for the feature bloat.
EDIT:
> Try to limit use of the walrus operator to clean cases that reduce complexity and improve readability.
Facepalm.
But now if our big paint point is assigning a variable to len(), then in two lines evaluating that variable and printing it out, Python is just trying to find stuff to add.
I miss the size and simplicity of old Python. :/
- Complex is better than complicated.
Using the walrus operator makes the code more complex, but less complicated.
Python is a fairly old, mature language.
What features would you have been especially excited about?
I'm also looking forward to PEP-554 [0], which allows for "subinterpreters" for running concurrent code without removing the GIL or incurring the overhead of subprocesses.
"end" statement which would enable automatic indentation.
"switch" statement instead of "if/elif/.../else" hell.
In particular, without the walrus operator you can't write an if-elif chain of regex comparisons. The best way to that that I've been able to find is with a bunch of nested if-else statements.
https://github.com/eveem-org/panoramix
(source code for Eveem.org, which is arguably the best decompiler for Ethereum smart contracts out there. you can see a lot of pattern matching in pano/simplify.py , and I found no way to do it without extending the language/walrus while maintaining the readability)
I went to your repo, and it wasn't clear to me what Panoramix actually is or does. So I checked out Eveem.org, and even on the about page, it wasn't clear to me what Eveem was.
But with that context, coming back to your GitHub page, I was able to get the gist of it.
It might be helpful to have a section above the installation section that gives a little context / tells about the project.
And in your example section -- again, I literally don't know anything about decompilation so maybe I'm not your target demographic... but if I understand the gist of it, kitties is an example function or binary. It might be better to use something more concrete (and provide the context).
Feels like a case of preferring cleverness over readability/usability/maintainability.
class Any:
def __eq__(self, other):
self.value = other
return True
Then you can use an instance of it as the hole in a pattern. My use case was performing peephole optimizations in a simple 6502 assembler. For example, if the LDA operation (load value from memory location into accumulator) occurs twice in a row, the first load is redundant. So I did this test: x, y = Any(), Any()
if L[i:i+2] == [('LDA', x), ('LDA', y)]:
L[i:i+2] = [('LDA', y.value)]
(Note that the last line changes the length of the list, so it may be slow on large lists.)I was going to write a blog post about this back then, but I never did. If anyone is interested, I can write it now.
Also, I wonder if there's an easy way to implement this in a jupyter notebook, without fiddling with the kernel...
(issues of whether it's a good idea aside, definitely seems useful to know about.. )
$ perl
$a = "foo";
if ( my $a = "bar" ) { print "$a\n" }
print "$a\n"
bar
foo if (ptr = strchr(address, ':'))
port = atoi(ptr + 1);I love the walrus operator with a love that is unholy.
(although I think "else" should have been a different keyword)
Agreed.
There's a number of (full) languages that compile to the Python AST [0] (I'm especially fond of http://hylang.org), but they're very different from Python. It would be interesting to see smaller variations of standard Python implemented in an interoperable way like these languages do.
[0] https://github.com/vindarel/languages-that-compile-to-python
I did not follow all the conversation around the typing system but isn't the whole point of it to propose optimizations in the future? I find it exciting
I also thought, why bother at first until I learned this is already a feature in python, but only for c-functions. So of course it makes sense to level the playing field.
They didn't select my preferred syntax, but I'm still looking forward to using assignment expressions for testing my re.match() objects.
I haven't used 3.8.0 yet but I hope its a good one because 2020 is the year that 2.7 dies and there will be a lot of people switching.
Of course I have to ask, what was your preferred syntax?
if re.match(pattern, string) as m:
#use m
Seems a bit more Pythonic, as "as" is already used like this with "with".Either one would be fine with me and useful.
The fact that this is the go-to example that everybody is using in justifying the introduction of the assignment expression convinces me that the real problem lies with the re module's API.
(To be clear: I will also be using assignment expressions for this case, but I don't think assignment expressions are really in line with the overall design of Python.)
if my_map.get(key) is not None:
// do something with my_map[key]I don't write much python so there's probably something obvious I'm missing, but I don't see why they didn't use "=". Is there some significant difference between assignment expressions and assignment statements that makes it worth having distinct syntax?
If x = 1
Has been the source of many errors in many languages. Forcing assignment to be more than a 1 character difference from the equality operator prevents this.Given that Python is statement-oriented, yes, having statements visually distinct from similar expressions is important.
It's also important to avoid making the equality operator and the assignment operator visually similar or easy to typo one for the other, which is arguably the bigger need for “:=” vs “=”, since “=” and “==” are quite similar and easy to accidentally mistype for each other.
[1] https://effbot.org/pyfaq/why-can-t-i-use-an-assignment-in-an...
Personally, I lost faith in the Python core team because of the Py3 migration. Yes, 3.x now has a bunch of nice features that 2.x did, but almost none of them actually depend on the 3.0's breakage (as proven by Tauthon).
If you want people to follow you through a break-the-world migration then you need to motivate why it is needed and why it couldn't be done incrementally, not try tempt them with a bunch of unrelated carrots that are bundled together with the breaking change.
def f(a, b, /, c, d, *, e, f):
Wow: IMHO that is a very ugly syntax to define a fn with 6 parameters, although it looks to be a path dependency on previous decisions (*, and CPython ,/) and clearly there was much discussion of the need for the functionality and the compromises: https://www.python.org/dev/peps/pep-0570/It amazes me to see how certain features make it into languages via community/committee (octal numbers in JavaScript - arrgh!).
One thing I find really difficult to deal with in languages is the overloading of different syntactic(edit:semantic) usages of different symbols, which itself is a result of limiting ourselves to the ASCII symbols that can be typed. I don't recall written mathematics having the issue badly (although I haven't had to write any for a long time!).
For https://www.oilshell.org/ , which has Python/JS-like functions, I chose to use Julia's function signature design, which is as expressive as Python's, but significantly simpler in both syntax and implementation:
Manual:
https://docs.julialang.org/en/v1/manual/functions/index.html...
Comparison:
https://medium.com/@Jernfrost/function-arguments-in-julia-an...
Basically they make positional vs. named and required vs. optional ORTHOGONAL dimensions. Python originally conflated the two things, and now they're teasing them apart with keyword-only and positional-only params.
A semicolon in the signature separates positional and named arguments. So you can have:
func f(p1, p2=0, ...args ; n1, n2=0, ...kwargs)
So p2 is an optional positional argument, while n1 is a required named argument.And then you don't need * and star star -- you can just use ... for both kinds of "splats". At the call site you only need ; if you're using a named arg / kwargs splat.
----
Aside from all the numeric use cases, which look great, Julia has a bunch of good ideas in dynamic language design.
The multiline strings in Julia appear to strip leading space in a way that's better than both Python multiline strings and here docs in shell.
Also Julia has had shell-like "f-strings" since day one (or at least version 1 which was pretty recent). Python has had at least 4 versions of string interpolation before they realized that shell got it right 40 years ago :)
Their saving grace is that humans are more intelligent interpreters.
If you ever try to translate a human-readable proof, even a fairly formal and rigorous one, into a computer proof assistant like Agda or Coq, you can see all the little ways humans cut corners.
I think we're ready for programming languages using some visually good Unicode characters, instead of overloading `[]{}!@#$%^&*()-_/` for everything!
A next step could be for common dev environments to actually convert symbol/key sequences to operators (how do APL programmers do it?)
Avoiding ambiguity and semantic overloading of ASCII symbols would surely help beginners (if also given a UI that clearly exposes ways to enter the new symbols). I always find one letter operators extremely strange too like u"word" s/foo/bar/ etc.
It seems a shame we can type in hundreds of Unicode symbols on a mobile virtual keyboard, but not readily on a physical keyboard.
JavaScript has supported Unicode for a long time, but the core language doesn't use it at all.
Some already do: https://docs.julialang.org/en/v1/base/math/#Base.:!=
if a ≠ b {
is allowed along with if a != b {
vfmt (kinda like gofmt) will be able to convert != to ≠.So far the response has been mixed.
Not everyone is open to this change.
[0] https://vlang.io
It will be a "good problem" to have the skillz to make this useful.
https://docs.python.org/3/whatsnew/3.8.html#f-strings-suppor...
{ k: eval(k) for k in ('theta', 'delta.days', 'cos(radians(theta))') }
This would trivially subsume the f'{user=}' syntax for the example given: just print out the dictionary. But it'd also be useful for: filling template dictionaries; printing out status pages for HTTP webservers; returning multiple variables from a function; flattening out complex data structures; creating dispatch tables out of local functions.You could even have a syntax like locals('theta', 'delta.days') and keep it familiar.
There's an Elixir library with macros to make a map (dictionary) using the variable names passed in [1]:
iex> import ShorterMaps
...> name = "Chris"
...> id = 6
...> ~M{name, id}
%{name: "Chris", id: 6}
Though wether its a good idea or not is another question. ;) If you want to do the type of programming you're talking about you should try Elixir/Julia/Clojure(/Rust?)... or any number of other languages with macros. def foo(bar):
if isinstance(bar, Quux):
# Treat bar as a Quux
elif isinstance(bar, Xyzzy):
# Treat bar as an Xyzzy
# etc.
I understand runtime type checking like that is considered a bit of a Python antipattern. With `singledispatch`, you can do this instead: @singledispatch
def foo(bar:Quux):
# Quux implementation
@foo.register
def _(bar:Xyzzy):
# Xyzzy implementation
With `singledispatchmethod`, you can now also do this to class methods, where the type of the first non-self/class is used by the interpreter to check the type, based on its annotation (or using the argument to its `register` method). You could mimic this behaviour using `singledispatch` in your constructor, but this syntax is much nicer.[1] https://docs.python.org/3/library/functools.html#functools.s...
[2] https://docs.python.org/3/library/functools.html#functools.s...
My immediate thought is that it's going to be hard for PyCharm to reliably point me to a function definition.
I am certain PyCharm is going to special-case these decorators in their next release.
It can also do dispatch on multiple arguments and on equality and has dispatch cache implemented as C extension (from my rudimentary measurements it seems that dispatch with cache hit is actually slightly faster than normal CPython method call).
> This module provides support for maintaining a list in sorted order without having to sort the list after each insertion.
I don't think I've ever cared to iterate over map values based on the alphanumeric ordering of their keys.
It's basically copy-on-write. The old and new map share all but O(log n) data.
You could probably do something like that with an unsorted map, but it's a good fit for a sorted one.
> A dictionary type with per-key types.
Ah, I've been waiting for this. I've been able to use Python's optional types pretty much everywhere except for dictionaries that are used as pseudo-objects, which is a fairly common pattern in Python. This should patch that hole nicely.
It was a major missed opportunity to provide a properly duck-typed Dict type, which by definition would allow untyped key-value pairs to be added to a "partially-typed" dictionary.
Gradual typing in general is a massive win for many kinds of real-world problem solving, but when you make it as hard as Python has to introduce partial types to a plain data object, you're leaving a lot of developers out in the cold.
I love MyPy and the static type hints since 3.6, but structural subtyping is superior and so obviously more Pythonic than nominal, yet support for structural subtyping keeps lagging behind.
> “Final” variables, functions, methods and classes. See PEP 591, typing.Final and typing.final(). The final qualifier instructs a static type checker to restrict subclassing, overriding, or reassignment:
> pi: Final[float] = 3.1415926536
As I understand it, this means Python now has a way of marking variables as constant (though it doesn't propagate into the underlying values as in the case of C++'s `const`).
The equivalent Java:
final float pi = 3.1415926536;For name mangling, I think you need to start your variable with underscores, and then it won't be accessible for reading outside the module either?
I understand that Python can't break all kinds of code (as the 2->3 conversion is still a pain), but still I imagine a Python-esque language without all the warts that Python have with it's 'organic' growth.
And you could 'import something from future' to opt-in to making those warnings errors right now.
I've been coding in algolesque languages for 20 years and hiding assignments inside of expressions instead of putting them on the left like a statement has always tripped me up.
I’ll just leave this here:
“There should be one—and preferably only one—obvious way to do it.”
Which is fine by me, it was a silly idea to begin with. What you really want is separated concerns that compose well, obvious here doesn't mean anything over there.
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")
Um, no it doesnt?: a1 = [10, 20, 30]
n1 = len(a1)
if n1 > 2:
print(f'{n1} is greater than two')Remember: every variable is an assignment into a dict, and every lookup a query into a dict. Reducing name lookups and assignments can yield good speedup in tight loops. For instance, caching os.path.join, os.path.split into local names can significantly speed up tight loops iterating over a filesystem. For example, os.path.split is potentially 5 dictionary lookups. Checking locals, nonlocals and globals for os. Then another to find path, and a final one for split. And this happens at runtime, for every invocation.
They have a lot of code samples and examples about how and when to use the new features.Personally, I love the new debugging support in F strings.
I don't really have a point, except that Python 3 feels like a moving target.
Considering this f-string example taken from another announcement:
f"Diameter {(diam := 2 * r)} gives circumference {math.pi * diam:.2f}"
This is valid Python 3.8, but it's not valid in Python 3.7 (no walrus operator). And removing the walrus operator still doesn't work in Python 3.5 (no f-strings). On top of that, other comments already mention how f-strings have lots of weird corner cases anyway.The entire point of Python in my circle of friends was that it made programming easy. Instead, I feel more and more in need of those "It works in my machine!" stickers. And good luck solving these issues if you are not a full-time programmer...
The minor releases are always backward-compatible, so just run the latest version and everything will work.
There's something to be said for a more stable, if less elegant, language.
f"Diameter {(diam := 2 * r)} gives circumference {math.pi * diam:.2f}"
If this is not write-only code, I don't know what is.Quite happy with the new SyntaxWarning for identity comparison on literals and missing commas :)
Especially neat is also the `python -m asyncio` shell which allows you to run top-level awaits in your repl instead of needing to start a new event loop each time!
Hard to understand why this is so far down. This is fantastic news!
Wow. I believe it's a stated goal of CPython to prefer simple maintainable code over maximum performance. But relatively low-hanging fruit like this makes me wonder how much overall CPython perf could be improved by specialising for a few more of these.
I really like the walrus operator, but I didn't realize how many "you shouldn't do this" and "this is too hard already" cases exist where they are discouraging using the walrus operator.
v = obj?.prop1?.prop2 ?: "default"
instead of long if conditions: v = obj.prop1.prop2 if obj and obj.prop1 and obj.prop1.prop2 else "default"
A PEP for Python 3.8 existed but has been deferred: https://www.python.org/dev/peps/pep-0505/Presentation: https://docs.google.com/presentation/d/1a3Zoav7NmeN_gXjGcV_l...
Video from PyBay: https://www.youtube.com/watch?v=OtdQN24Z5MA
It covers assignment expressions in some depth, as well as highlights elsewhere.
if m=whatever():
do_something
Why create a new assignment operator? For all the talk about making code not confusing, etc., some of these decisions sure seem nonsensical.Oh, OK, it confuses passing arguments into a function by name? Really? C'mon.
Also, I can't understand the nearly religious rejection of pre and post increment/decrement (++/--) and, for the love of Picard, the switch() statement.
I enjoy using Python but some of these things are just silly. Just my opinion, of course. What do I know anyhow? I've only been writing software for over thirty years while using over a dozen languages ranging from machine language (as in op codes) to APL and every new fad and modern language in between.
As I watch languages evolve what I see is various levels of ridiculous reinvention of the wheel for very little in the way of real gains in productivity, code quality, bug eradication, expressiveness, etc. Python, Objective-C, Javascript, PHP, C# and a bunch of other mutants are just C and C++ that behave differently. Sure, OK, not strictly true at a technical level, but I'll be damned if it all doesn't end-up with machine code that does pretty much the same darn thing.
The world did exist before all of these "advanced" languages were around and we wrote excellent software (and crappy software too, just like today).
What's worse is that some of these languages waste a tremendous amount of resources and clock cycles to do the same thing we used to do in "lower" languages without any issues whatsoever. Mission critical, failure tolerant, complex software existed way before someone decided that the switch() statement was an abomination and that pre and post increment/decrement are somehow confusing or unrefined. Kind of makes you wonder what mental image they have of a programmer, doesn't it? Really. In my 30+ years in the industry I have yet to meet someone who is laid to waste, curled-up into a fetal position confused about pre and post increment/decrement, switch statements and other things deemed too complex and inelegant in some of these languages and pedantic circles.
Geez!
</rant off>
Something like:
r1 = foo(x:=bar)
r2 = baz(x)`f'{username= }'` 'username="tolgahanuzun"'
I like it.