Edit: Actually that's already true with string formatting.
Edit: Actually that's already true with string formatting.
(1) It doesn't make the language less efficient
(2) The new feature is coherent with the rest of the language
(3) It doesn't make learning the language harder
(4) It brings joy in my life (as f-strings do)
Then.. it's ok :-)
Time to refactor the project I am currently working on that is loaded with `str.format()`
s = "this is {}"
print(s.format("possible"))
but you cannot do that with f-strings because they are resolved on definition, so you could do something like this: for message in messages:
print(f"The message is {message}")
But not this: s = f"The message is {message}"
for message in messages:
print(s)
This is the same drawback of JavaScript's Template Literals [0].EDIT: Fix syntax in examples.
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
x, y = 1, 2
print(f'{x=}, {y=}')- Tooling, code analysers, etc. have to be updated to understand the new features. Tools which are dead or "finished" may stop working.
- The possibility that someone might use a new feature can break guarantees that are relied upon by existing code. An obvious example would be adding try/catch to a language: in languages without this, a function call is guaranteed to either return control to us, or to run indefinitely. Libraries might rely on this to clean up file descriptors, etc. afterwards. Such libraries would be broken by the mere existence of try/catch; even if we never actually use it.
That said, I'm a big fan of pattern-matching, so it would be nice to have in Python, rather than crude approximations like '{pattern1: A, pattern2: B, pattern3: C, ...}[myVal]'
[0] https://discuss.python.org/t/gauging-sentiment-on-pattern-ma...
Three string formatting approaches are all in wide use. There are now two different ways to do something as fundamental as assignment. Some classes use explicit methods like __eq__, others hide them by using @dataclass. Async-await adds coloured functions [0], along with “red” versions of numerous language constructs.
That’s not to mention type annotations, which I won’t deny are popular but are incredibly complex.
[0] http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
[[None for _y in range(y)] for _x in range(x)]
for two dimensions is fine, while [None for _x in range(x) for _y in range(y)]
(to create a flattened 2-dim list? excuse the dummy example) is just begging for later headache. Explicit looping is much more readable. Note the flipped order of expressions -- these two statements create the same iterators.Not to mention, you can even reuse the name for extra evil:
[i for i in range(2) for i in range(3)]
results in [0, 1, 2, 0, 1, 2]Too much mental overhead. Other people love that kind of stuff though.
Within a few seconds I tested that theory, by populating a list with dicts and voila - just worked. Close to the last day I ever touched Perl.
The python language expansions that have come recently, f-strings, walrus operator, and now matching - are great in that they don't make the language more complex - all three of these are easily explainable in a few minutes to the novice, and once they understand it, they can quickly (and profitably) incorporate it into their code.
I wan't there to be a steady drumbeat of these improvements that let me write more elegant code, more concisely.
Try and find a single python developer who would give up their f-strings now.
At one time, a trainer was trying to tell us Ruby was the new hotness (2009 or 2010, I think). I liked Ruby OK, but Python seems cleaner and we stuck with it. I have not felt the slightest regret.
Honestly, that sounds like someone that didn't bother to learn what's actually going on and understand what the language was doing. I mean, that's fine, we all do that... but I wouldn't blame the language for that. Perl makes nested data structures very easy and obvious once you learn what's going on. You just need to actually learn it and not rely on the little shortcuts Perl allows you to use to never make that next step to learning exactly why.
Perl will "just do what you mean" so much that it papers over some of the early learning friction points enough that they build up to a later point. At that point, people either throw up their hands and move on because it's annoying when some shortcut they've relied on isn't working in this one weird instance, or they learn what's really going on (Hint: it's almost always either about understanding references or context at this stage) and it's no longer a problem.
> Try and find a single python developer who would give up their f-strings now.
Things like f-strings and list comprehensions in Python are the exact thing that Python devs used to point out as too complex in other languages (I mean, f-strings look to be pretty much just string interpolation). I think that's evidence that perhaps a language isn't always best suited for all audiences. Python used to be aimed at learning and easy of understanding and use. Now that it's often more targeted for complex engineering projects, you get stuff sneaking in that was specifically avoided by design initially.
The point I'm trying to make is not that I'm a good or knowledgeable developer (neither of which I am, despite having written tens of thousands of lines of perl) - but that the core essence of Python, is that people can use it quickly and profitably without being one. The cognitive hurdle to start using objects in python is tiny - and, once you get that - a lot of the stuff that you would hope works, just does. The language is very friendly to novice coders, and it's lack of implicit (for the most part) actions avoids a lot of unclear side effect.
For more complex projects, things like decorators and generators and type-hints, which are advanced, are available if you need them - but you can go a long way (sometimes forever) without ever touching them - that's not the case with simple data structures - you pretty much need to start working with HoA in perl if you want to do complex things, and I know people who have used perl for the better part of a decade, who have never done so - and were blocked from doing more interesting things.
The simple answer that probably would have solved almost all your problems is that you can use the reference syntax for defining things and it will look like and function the same as JavaScript 95% or more of the time, and the only time you'll need to do anything is when passing it to something that expects an actual array or hash. I mean, you can get away with taking JSON and changing colons into fat arrows and booleans into 0 and 1 and it will just work as a Perl data structure as is like 99.99% of the time. Data structure manipulation works similarly as well for the most part.
I still use, and love Perl. If you stick close to the original inspiration of using it to process text, it flows very naturally and isn't hard to read or understand.
I do get that the proliferation of sigils ($,@,%), breadth of built-ins, things like $_, complex dereferencing, and overall terseness are a visual putoff.
It may be that I still like it because it uses mostly the same functions as 'C'...things like stat(), getpwent(), ioctl(), and so forth that were already in my head. And it was way less tedious for strings and text, and no malloc/free to keep track of.
But... after I learned Scala a bunch of years ago, working in any language without pattern matching has felt like significantly more of a chore than it used to be. It's like you've been doing something the hard way for years, someone teaches you an easier, better, more readable way, and just when you get used to it and love it, then tells you that you can't use it anymore.
Certainly that's not true of all possible high-mental-load new features that could be added to Python. But I don't want to believe that features that drastically increase ergonomics shouldn't be added to a language because they make the language less simple.
Isn't there supposed to be only one obvious way of doing these things?
See it as deferred refactoring for library maintainers. Library maintainers that work in a monorepo with their users get this refactoring feature for free.
And also, the issue isn't really with library maintainers when it comes to Python syntax changes. It's with all the user code written in Python. I tried converting my programs from Scala 2.12 from 2.13, and I failed so hard. I couldn't justify the time commitment, even with tools like scalafix to automate stuff. It's just not that smart.
All this is to say, let's not pretend that breaking changes to a programming language are ok. If you have to do break stuff once or twice in the lifetime of your language, fine. But accepting breaking changes as the status-quo is a failure of a language, by definition, IMO.
There is a huge benefit to keeping existing code working as it is. Both for businesses and the ecosystem. It benefits everyone if the language does everything it can to keep that intact.
Deprecated language features are, by definition, still present and need no special incantation to miss, though they may produce Deprecation warnings to encourage you to update your code. Removed features would, I guess, be something you could do this for, except that generally the whole benefit of removing features is getting rid of the supporting code and the effort of maintaining it and it's interactions with new code.
greeting = "hello {}"
greet = partial(str.format, greeting)
greet("world")
How would you pass a variable format string to f"" ? >>> flt = 1.2345
>>> two = 2
>>> f"{flt:.{two}f}"
'1.23' greet = lambda x: f"hello {x}" greet = partial(str.format, greeting)
greet("world")
as opposed to just greeting.format("world")
? greet = greeting.format greet_fstring = partial(lambda _,s: f"hello {s}", greeting) greet = lambda x: f"hello {x}"Surely this would not work:
greeting = "hello {x}"
greet = lambda x: f greeting code = 'f"{input()} {x}"'
lambda x: eval(code)
(Please don't do this) code = 'f"here it is {eval(input())}"'
(lambda: eval(code))()
k, there we goI think the advantage of switch statments and pattern matching is that you know the scope of the thing you're switching or matching on, but you don't have that restriction in if/elif/else blocks. There's nothing keeping me from adding a condition like, `elif day == "Tuesday":`, that has nothing to do with the task at hand. When I see switch or match, I feel like I know what I'm getting into.
Brainfuck is an extremely simple language. I mean, it's perfect. Only 8 instructions! Only 1 pointer! Turing complete! Why isn't everyone writing their code in this language?
A bicycle attached to a wheelbarrow is extremely simple transportation. You can get anywhere you need to go, you can carry heavy loads, it's easy to use, easy to fix, cheap. Why would you use anything else?
Wood is a great building material. You can build a building several stories tall, it's cheap, plentiful, easy to work with. We should just standardize all construction on wood.
Water is the simplest beverage. It's clean, healthy, easy to ship, easy to store. We don't need to drink anything else.
https://docs.python.org/3/reference/expressions.html#lambda
https://262.ecma-international.org/11.0/#sec-arrow-function-...
https://docs.oracle.com/javase/specs/jls/se15/html/jls-15.ht...
That's a funny one. The whole history of C++ is being backward compatible, originally with C.
f'gcd({a}, {b}) = {math.gcd(a, b)}'
is so much better than 'gcd({}, {}) = {}'.format(a, b, math.gcd(a, b))
In the .format() version, the formatting and the values are separate and that makes it more clear to see both the formatting and the values. I really like that separation. When the expressions get more complex, .format() doesn't really lose any clarity while f'' gets pretty unreadable quite fast IMO. Yes, you can simplify by assigning the expressions to variables, but .format() doesn't even need that.It makes it harder to see which values actually go where though. I really dislike the separation.
That's obviously a pretty fundamental difference in point-of-view. And it looks like your point-of-view is shared by a large majority.
"The reasonable man adapts himself to the world. The unreasonable man persists in trying to adapt the world to himself. Therefore, all progress depends on the unreasonable man." -- George Bernard Shaw
I try to be reasonable, so I guess I should try to come to terms with f-strings. In any case please don't depend on more progress.
You can name the variables in the format string and then pass the expressions as keyword parameters.
>>> a, b = 2, 4
>>> f"{math.gcd(a, b) = }"
'math.gcd(a, b) = 2'And I really dislike format where the arguments are given by position. For short strings it's not going to matter but for long strings you're bound to mess something up sometime. Just imagine writing some kind of long email template and ending up with something like:
'''Dear {}{}
I write you on the {} to inform you...
Kind Regards,
{}'''.format('Mr. ', 'President', '1st of April', 'Ohio', '12', ..., 'Automated mail bot')
Okay it's a bit contrived but any long multiline formatted string is going to be hard to read without using keywords.And once you insist things are named then f-strings look a lot neater since you get:
f'gcd({a}, {b}) = {math.gcd(a, b)}'
v.s. f'gcd({a}, {b}) = {gcd_a_b}'.format(a=a,b=b,gcd_a_b=math.gcd(a, b))
Of course sometimes a template is the appropriate tool, but usually because it's going to be used more than once.Is that enough?
So we come to readability, and I have to say that to me .format() is more readable. Honestly.
Regarding runtime errors, I presume you mean .format() doesn't prevent mismatches between the number of placeholders and the number of arguments, as in '{} {}'.format(42)? That's indeed a point in favor of f'', that I hadn't previously considered.
Readability is a win in my book. You mention too much is happening in your example string. Ok, perhaps it is. But remember, just because the interpolation supports expressions, doesn't mean you have to put them in there. I'd argue your function call would be better done on the line above, with only the result put in the string. This is the most readable version so far I think:
c = math.gcd(a, b)
f'gcd({a}, {b}) = {c}'
Syntax highlighting helps out as well.Yes about placehoders. Also that in some cases of format() each variable is repeated a number of times:
'hello {name}'.format(name=name)
When this is changed it is a bug magnet. DRY in a nutshell. Rarely is a feature a home run like this one. Not to mention I was using it in batch files, shells, and perl twenty-five years ago.Like you - and I just learned about the existence of f-strings tonight - I find .format() to be much more readable and logical than f-strings.
My first impression reading about f-strings was "ew."
It's not a case of "I don't like it because it's new". To my jaundiced and admittedly subjective eyes, I'm still finding .format() better, precisely because it doesn't lose any clarity in exactly the way you stated.
In other words, I just don't get the point.
Furthermore! f? Really? What the f does f mean? The str() function is the str() function. As well as print(). As well as .format().
f? please
Find me a modern language where that isn't true, though.
"There are only two kinds of languages: the ones people complain about and the ones nobody uses."
I think it's an interesting point you make, because Python is actually one of the few languages that tried to break away from its past (the whole 2.7 to 3.7 fiasco), but it seems the need for a language to be stable is just much, much greater – so features are just piled onto the existing standard instead.
If COBOL taught me one thing, it's that I'm pretty sure in 60 years time there will still be critical infrastructure running on Python 2.7.
My old boss used to say this thought terminating cliche whenever I encountered a problem with PHP. It's a rather useless form of binary thinking, since it completely ignores scale and severity.
It's similar to statements like "all politicians are bad", which put the likes of Nick Clegg in the same bag as, say, Pol Pot. It's counter-productive to any meaningful discussion.
You may also be interested in: "there are 10 kinds of people - those who understand binary, and those who do not"
In most cases I have encountered people uttering this quote they were using it as a mere observation of the statistical fact that a large group of users will inevitably lead to a larger amount of complaints. And the conclusion was usually something like: "Don't blindly disregard a language, because many people hate it. Always take a closer look if the tool is up to the job."
Java => Kotlin is an example of a migration that's relatively painless, by design, and lets you incrementally convert projects over, source file by source file. But the Kotlin designers intentionally left alone certain misfeatures of Java to make that happen. It's still UCS-2 rather than UTF-8, for example, and it syntactically papered over nulls rather than actually fixing them with a true Optional monad like more recent languages, and its coroutine/async/await semantics aren't as elegant as Go or ES6 or Python 3.6. Changing runtime semantics of an existing language ecosystem is hard.
C++ is a wholly different beast. Template metaprogramming achieves great things, but at significant cost to readability.
(Python 3 got rid of old-style classes and made the two syntaxes equivalent.)
example: pylint-type tools that flag old ways of doing things, and suggest replacement syntax (e.g. https://pycodequ.al/docs/pylint-messages/r1717-consider-usin... )
My first thought whenever someone proposes changing a language is "could that be done with a library instead?". If it's possible then I think libraries are preferable to language changes; if it's not possible and we decide to change the language, then it may be better (if possible) to make a change which fixes whatever prevented us from implementing such a library. Only if we can't do that would I resort to adding individual features to the language.
For example, Scheme has really good pattern matching, despite it not being part of the language. In fact, there are so many pattern matching libraries for Scheme that there are now attempts to standardise their APIs https://srfi.schemers.org/srfi-200/srfi-200.html
I hate how different reading C++0x and C++17 is. I hate reading a "C++ style guide" for a new project.
I already feel that pretty soon, "Python Style Guide" will be a necessary evil...
Might be that the whitespace indentations focus the mind. Also: PEPs already act as style guides. And if you are worried to do it wrong, just run it through a code formatter like black. It really isn't that much of a problem IMO.
And it looks like Python is heading in the same direction. It's not difficult to imagine a future where some Python projects are going to want such guides too.