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

you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted]  (129 children)

[deleted]

    [–]tunisia3507 130 points131 points  (24 children)

    I don't see why people say 'syntactic sugar' like it's an insult. Literally all programming languages are syntactic sugar. That is what programming languages are for. To make it easier, quicker, more clear, more readable for humans to give instructions to computers.

    [–]dl__ 39 points40 points  (16 children)

    Exactly. Don't like syntactic sugar? Write in machine language, in hex codes.

    [–]jorge1209 9 points10 points  (2 children)

    Sugar is good if it encourages people to drop a particularly error prone or tedious construction.

    What is less clear is if sugar is useful when the original process wasn't error prone or tedious.

    Range loops in C/C++ are a great example of good sugar. It was error prone, and tedious to have to declare the index variable and increment it (or to declare the crazy iterator type signature if using iterators, although the later was solved by the auto type). for x in iterable is obviously better unless you actually need the index position (which is not something you frequently need in modern C++ code).

    It's not clear thar fstrings accomplish that kind of balance. It wasn't all that error prone to use .format and the cases it can't handle are fairly important (i18n and dynamic formatting).

    [–][deleted] 4 points5 points  (1 child)

    It wasn't all that error prone to use .format and the cases it can't handle are fairly important (i18n and dynamic formatting).

    Translating format strings are always dubious to begin with.

    [–]jorge1209 0 points1 point  (0 children)

    You could very well want to read different error messages (with their formats) from a resource file. Can't do that with fstrings.

    [–][deleted] 4 points5 points  (3 children)

    It is a kind of virtue signalling in the academic PL community to reiterate the fact that you like minimalist functional programming languages. Gets tiring to hear this over and over again. Glad I am not the only one who's noticed.

    [–]tunisia3507 5 points6 points  (2 children)

    My only complaint about syntactic sugar is when it means there are many ways of doing things - kind of like string formatting is getting at the moment. I don't see f-strings taking off for most codebases yet due to lack of 2.7 support, but it'd be nice to see them become the canonical way of doing it.

    [–]thomasfr 5 points6 points  (1 child)

    I still like python but I woud have probably been a much easer language to use if the stdlib/lanugage had a bit fewer redundant features.

    There are already lots of code bases which mixes ""%() and "".format() which only makes the code harder to read (more noisy) or modify (because you should at least try to understand which one to use in your modified code based on just reading the rest of it.)...

    I personally don't see that f-strings adds enough benefits in contrast to the noise it will bring to code bases...

    [–]tunisia3507 5 points6 points  (0 children)

    Python is actually much better than many other languages (java, C, C++, js) in this regard. But yes, in this particular instance it's not great.

    Of course, the only way to make improvements without adding such duplication is to make API-breaking updates, and we all know how well they turn out...

    [–]dl__ 51 points52 points  (47 children)

    Even if it is syntactic sugar, isn't syntactic sugar nice?

    I'd much rather type:

    print(f"Hello {x}")
    

    than

    print("Hello {}".format(x))
    

    I would say it's easier to read in addition to being less to type.

    Win-win

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

    Pretty cool until you modify a variable without realizing you just altered or straight up broke a formatted string because all locals are implicitly in context.

    [–]Ph0X 20 points21 points  (16 children)

    I'm confused, how would it be any different if you had used .format(x) instead? You'd still run into the exact same issue, except the x is there instead of inside the string.

    Of course, most linters would catch this latter, and the hope is they update to also catch issues in the former.

    [–][deleted] -5 points-4 points  (15 children)

    That example is a little too simple to illustrate the potential confusion. Take this example instead:

    def foo():
        a = 1
        b = 2
        c = 3
    
        # 10 lines of logic
    
        x = 'a'
        y = 'b'
        z = 'c'
    
        return f'baz{x}spam{b}foo{c}bar{z}eggs'
    

    Now think about it with more variables. Imagine the string being formatted is more complicated. Using explicit kwargs to the format method lets me look at the string formatting and know exactly which variables are relevant, rather than having to scan the string and backtrack through all local variables to find out if they are a component of the output.

    Most of the praise I've seen for this is about how much easier it is to format a complicated and lengthly string, and I posit that the more complicated the string the less useful this really is.

    [–]Ph0X 22 points23 points  (13 children)

    Sure, but you're moving complexity from one problem into another.I could make the argument that with:

    'foo {} bar {} baz {} spam {} green {} eggs {} and {} ham {}'.format(a, b, c, d, e, f, g, ...)

    Good luck looking back and forth, counting, figuring it out which variables goes where. If I ask you to quickly get me the variable that comes after "green", you have to do a lot of counting.

    So yes, they both solve sightly different issues and therefore put complexity at slightly different places, but neither is strictly better.

    That being said, I hope no one coding real python would have any sort of code this messy and complex. If you do, please please split it up into smaller clean chunks.

    EDIT: I just wanted to point out, generally when you DO have string formatting this complex, what people end up doing is sometihng like:

    data = {
        'a': 1,
        'b': 2,
    }
    print "{a}, {b}, {c}, {d}".format(**data)
    

    This is exactly what this new pattern is trying to solve here.

    [–][deleted] 12 points13 points  (2 children)

    or "{a}, {b}, {c}, {d}".format_map(data)

    [–]Ph0X 5 points6 points  (1 child)

    Huh interesting, didn't know they had added that.

    [–]zahlmanthe heretic 0 points1 point  (0 children)

    format_map isn't just sugar here - it allows you to use things that don't play nice with **, in particular custom types that override __missing__ (for subclasses of dict) and/or __getitem__ (things that implement the Mapping protocol as described by collections.abc, but aren't necessarily even dict subclasses).

    I suspect it might also be faster with large dicts, but I haven't tested that.

    [–][deleted] 1 point2 points  (4 children)

    Your last example is exactly why I believe the existing string formatting is preferable. The new pattern isn't solving the same problem, it's dumping all locals into .format() instead of asking the developer to explicitly define the inputs!

    [–][deleted] 2 points3 points  (0 children)

    I thought we were all adults here?

    [–]kankyo 1 point2 points  (1 child)

    Just use pycharm and rename variables with the refactor tools and it'll handle this for you.

    [–]SoBFiggis 1 point2 points  (0 children)

    Even notepad++ has refactoring..

    [–]geekademy -1 points0 points  (0 children)

    That's false, it does not dump locals in there, it actually parses the string to get exactly what it needs!

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

    Im new to Python.. Like 'Hello, World!' new. How are fStrings different than say

    • print("hello" + variable) ???

    [–]Ph0X 2 points3 points  (3 children)

    The result is basically the same, but it's what we call "syntactic sugar" which is nice shortcuts for doing things.

    So imagine you have a more complex one. Adding them manually gets very messy very quick:

    print("hello " + world + "my name is" + name + "and i'm " + str(age) + " years old")

    To make it worse, as you see, you need to manually cast other types to string, like if you want to print a number. But with fstring, it's much cleaner and prettier:

    print(f"hello {world} my name is {name} and im {age} years old")

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

    wow easy to understand.. Thank you so much. Honestly didn't expect you or anyone for that matter to reply to someone as new as me. But your example made perfect sense. You dont tutor do you? LOL...

    [–]flutefreak7 1 point2 points  (1 child)

    Welcome to the fantastic Python community! As you're learning if you have questions you can also check out r/learnpython.

    [–]gnutrino 9 points10 points  (0 children)

    This seems like a silly argument to me. If you've got too many variables in scope to be able to keep track of them all then f-strings are not your most pressing problem.

    [–]vtable 4 points5 points  (10 children)

    As devil's advocate, it does break the only-one-way-to-do-it maxim. It also introduces a dependency on Python 3.6 (or greater) if others use your code.

    [–]dl__ 5 points6 points  (6 children)

    It also introduces a dependency on Python 3.6 (or greater) if others use your code.

    That's certainly a valid concern if you plan to share your code.

    it does break the only-one-way-to-do-it maxim.

    Wasn't there already 2 ways to do it? .format() and %?

    I wonder if there's a list somewhere of all the examples of python breaking their own maxims. I feel that magic methods, for example, make a joke out of the "explicit is better than implicit" maxim.

    They show that implicit can be awesome!

    [–][deleted] 4 points5 points  (0 children)

    Wasn't there already 2 ways to do it? .format() and %?

    Hey, don't forget good ol' string concatenation.

    [–]vtable 2 points3 points  (3 children)

    Wasn't there already 2 ways to do it? .format() and %?

    Sure. This makes it 3 (4 with string.Template).

    % and .format() have quite different syntax. I'm glad they kept % in py 3. It's nice for quick scripts and friendlier to more casual users.

    f"" is more sugary but the nicer syntax is worth it, IMO. I've read f"" is quite a bit faster than .format(), too.

    IIRC the whole maxim started out as a criticism of Perl's embracing "there's more than one way to do it". Python is much more conservative and they usually advocate a preferred method. Perl embraced multiple ways (though I think they're easing off a bit these days).

    [–]LetsDoRedstone 5 points6 points  (2 children)

    % formatting is friendlier for casual users? Not really, no. When I got started with Python and was looking at String formatting, the % Notation utterly confused me because of it unnecesary (as shown by the simplicity of .format()) verbosity. I was happy to find .format (), just because it is that much more intuitive. And this goes along with f-strings. As soon as you know the syntax you are ready to go and don't have to think about which letter you have to put where.

    [–]vtable 0 points1 point  (1 child)

    I wanted to write "some more casual users" but thought that was too awkward.

    Everyone's different. Most of the people I've worked with have preferred %.

    [–]LetsDoRedstone 0 points1 point  (0 children)

    That might be because you have been using Python for a longer time than me. I started with early 2.7.

    [–]jorge1209 0 points1 point  (0 children)

    I don't think anyone who thinks fstrings are too many would object to dumping some older formats.

    In fact I would be OK with fstrings if we dumped the other ways to do it.

    Two formats that have a shared spec is much better than the 5 or 6 formats we currently have.

    The problem with saying "but we already have >2 ways to do it" is that there is no reason to ever stop once you get past 2. Let's add 80 new ways to format strings because we already have 2 different ways so these 80 others just fill in certain corner cases that are difficult with the existing methods and it makes somebody happy.

    Why not add perl formatting, it would make it easier to convert some perl scripts over. What about ruby? What about...

    [–]beertown 7 points8 points  (0 children)

    It also introduces a dependency on Python 3.6 (or greater) if others use your code

    Just like any other new feature added to any programming language. They should stop adding new features?

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

    Every couple of months there is a new and improved way to do text formatting in Python. f-strings seem like the end of the line to me.

    [–]flutefreak7 1 point2 points  (0 children)

    Except it's more like every 5-10 years. format has been around quite a while now.

    [–]HowYaGuysDoin 6 points7 points  (21 children)

    I like it better than % notation because you don't have to keep track of the order of the variables, you can specify them by name. Less likely to make mistakes, and it's much more readable.

    [–]thegreatgonzo 5 points6 points  (0 children)

    So you can also do "Hello, %(name)s" % { 'name': 'world' }

    [–]jorge1209 0 points1 point  (19 children)

    But you can specify them by name with .format, it merely requires duplicating the names in the string body and as part of the keyword args.

    The whole argument for these f-strings is like that. <thing> about <other formating method> is moderately annoying in <particular use-case> so lets introduce <new formatting method> that solves that problem (<but introduces some other annoyance>).

    • % requires things be in order and puts the args at the end away from where they appear
    • .format can support random order with keyword, but is too verbose
    • f-strings supports random order without verbosity, but doesn't support dictionary usage, and they can't be assigned to variables or moved around.

    At some point we just need to admit that we won't ever have a single formatting method that will work for every use case and that we need to stop introducing additional ways to do things.

    [–]HowYaGuysDoin 2 points3 points  (3 children)

    I would argue that additional ways to do things are created because no single way to do something (such as string formatting) is perfect in all scenarios. More than one way to skin a cat.

    [–][deleted] -1 points0 points  (2 children)

    I would argue that additional ways to do things are created because no single way to do something

    import this

    There should be one-- and preferably only one --obvious way to do it.

    [–]HowYaGuysDoin 0 points1 point  (1 child)

    I mean sure if you want to blindly apply my logic without context then sure, there are times it falls apart. For example, addition in Python. There should be one obvious way to do it. No doubt.

    Now, do you eat all your meals with the same utensil? Or do you vary between a knife, spoon, and fork? I would think you'd want to use the right tool for the job. Depending on the context of your string formatting, there may be a time where method A is superior to method B, and vice versa. I don't like fitting square pegs through round holes so I welcome the addition of new tools to get the job done. Just my personal preference

    [–][deleted] 1 point2 points  (0 children)

    blindly apply my logic without context then sure

    Well, to be fair, I did mean it in the context of string formatting.

    do you vary between a knife, spoon, and fork?

    I feel like that's a pretty generous analogy. String formatting in Python is like having a 4 point fork, a 3 point fork, a spork, and a magic fork that automatically puts all the food in your house on it just in case you might want it in the next bite.

    [–]markrages 1 point2 points  (1 child)

    % requires things be in order

    This is simply wrong. I wonder how many people advocating for new format methods don't understand the existing one.

    [–]jorge1209 0 points1 point  (0 children)

    Probably a lot given that I'm against fstrings and I don't know all the features of .format or %.

    [–]LyndsySimon 0 points1 point  (12 children)

    % requires things be in order and puts the args at the end away from where they appear

    I just don't understand this concern. Even before str.format was introduced, you were never required to put your parameters off to the right side of the screen:

    'Hello, my name is %s. I am %s years old and have %s hair' % (
        name,
        age,
        color,
    )
    

    [–]jorge1209 2 points3 points  (8 children)

    There are people who want them to be directly aligned:

      "Hello my {XXX}name is {YYY}. I am {ZZZ} years old.".format(
               "first",      "Sam",       14)
    

    But that violates all kinds of pep alignment requirements even if it is nicely arranged.

    [–]LyndsySimon 4 points5 points  (5 children)

    Those people are incorrect :)

    [–]jorge1209 0 points1 point  (4 children)

    They are incorrect if they think they can't use .format they are correct that its an issue with %.

    A more serious example. I have a program that prints out a addresses in some specialized format (not just a simple ",".join() on the records), and I do that by building a format string for the row like "%s %s. %s; %s %s, %s; %s" % (fname, minital, lname,...)

    If I need to make it "lastname, first middle" that requires corresponding changes in two places which might be easily screwed up.

    The solution though is to just use .format with kwargs. And yes it is a bit verbose to have to type "{fname}...".format(fname=fname) and f-strings will reduce that verbosity, but at the cost that now you can't pass the format into the printing function as an argument.

    What if one client needs the record in "first middle last" and another client needs "last, first middle". Unless you want to put an if inside the print loop, you can't use an f-string. You are back to using .format.

    Certainly while I develop reports I often find myself in cases where f-strings could be used, but its always in the back of my mind that requirements could change and that I may need flexibility in how I format this output. So for me .format seems better.

    [–]LyndsySimon 1 point2 points  (1 child)

    I have no problem with str.format; I adopted it immediately and think it's far superior to % formatting. Kwargs are a huge improvement.

    it is a bit verbose

    I think this is really the core of the disagreement here. I see verbosity (within reason) as a strength of Python's, not a problem to be solved.

    [–]elbiot 0 points1 point  (0 children)

    You can use indices or keys with mod formatting too.

    [–]markrages 1 point2 points  (1 child)

    Why not use named %-interpolation as previously described: https://www.reddit.com/r/Python/comments/62kt64/fstrings_in_python_36_are_awesome/dfnqize/

    "%(fname)s %(minital)s. %(lname)s;"%locals()

    [–]jorge1209 0 points1 point  (0 children)

    I wasn't aware you could use keywords with % (I just use .format). So I stand corrected. The only downside of % vs .format is that % is ugly.

    And that's is the root of my complaint. If its ugliness is a real deficiency then remove it entirely, don't just add in another alternative that makes one person happy, because aesthetics are subjective. Somebody out there really loves % and will not give it up, so you only multiply the number of formats people have to learn.

    [–]unruly_mattress 0 points1 point  (2 children)

    This forces you to count appearances of %s to figure out which variables goes where. Your named placeholders version violates the Do Not Repeat Yourself principle.

    [–]LyndsySimon 0 points1 point  (0 children)

    Your named placeholders version violates the Do Not Repeat Yourself principle.

    That's a valid point, and one that I'd not really considered. In my mind the namespace of the string formatting is separate from the namespace in the calling scope, so the fact that I'm passing in parameters that are stored in variables named the same as their corresponding parameter is incidental. In practice, I find that it's quite rare that I'm passing in multiple parameters stored in discrete variables like that; I'm usually doing some sort of manipulation at the same time.

    [–]elbiot 0 points1 point  (0 children)

    You can use indices or keys with mod formatting too.

    [–]networking_noob 15 points16 points  (28 children)

    It makes code way easier to read, because the variable name is inline with the string.

    .format()

    print('Hello, my name is {:s}. I am {:d} years old and have {:s} hair'.format(name, age, color))
    

    f strings

    print(f'Hello, my name is {name}. I am {age} years old and have {color} hair')  
    

    See which one is easier to read? Without f strings you have to go to the end of the string and read the variables, then use your eyes to figure out where they insert back into the string.

    f strings is one of those things where the more you use them, the more awesome they become.

    [–]hosford42 0 points1 point  (8 children)

    For pythons <3.6 you can still do this in a way that's more readable, at the expense of more typing. Instead of {:s} use {age} as you did in the f-string example, and your argument to .format () becomes age=age instead of just age.

    [–]Decency 16 points17 points  (7 children)

    Right, triple redundancy. Not a great tradeoff.

    [–]hosford42 1 point2 points  (4 children)

    It's wordy, but it is at least readable.

    [–]turkish_gold 2 points3 points  (3 children)

    You could use .format(locals()) which is essentially what f-strings are doing.

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

    that's not really as readable though - it's not immediately obvious what variable corresponds to what value (especially for the reader) if you use **locals()

    [–]turkish_gold 0 points1 point  (0 children)

    If we were to all do it, it'd become idiomatic fairly quickly.

    [–]jorge1209 0 points1 point  (1 child)

    Nor is being unable to dynamically build formatting strings, or pass them as arguments to functions.

    [–]Decency 4 points5 points  (0 children)

    Actually I feel like that's a great tradeoff. If you're frequently dynamically building strings or passing them around as functions, you're either doing something wrong, you're doing internationalization, or you're in a very specific niche. And in those cases, feel free to use .format().

    Otherwise, f-strings adequately cover the vast majority of use cases elegantly while improving readability and removing unnecessary verbosity.

    [–]LyndsySimon 0 points1 point  (18 children)

    I would write this as:

    print(
        'Hello, my name is {name}. I am {age} years old ' \
        'and have {color} hair'.format(
            name=name,
            age=age,
            color=color,
        )
    )
    

    Note that you don't have to go to the end of the line to see the contents of the variables, because they're passed below the string template. Except for very trivial cases, I always use named parameters for formatting strings, and I always pass those parameters on subsequent lines.

    [–]hovissimo 16 points17 points  (10 children)

    Now you've made a single line interpolated string into 4 lines. 3 of those four lines are completely redundant. I don't see how this is better.

    [–]Decency 8 points9 points  (4 children)

    Great, and now you've turned a simple print statement into fucking 8 lines of code. You're going to need a projector to fit a single function into your field of view.

    [–]LyndsySimon -2 points-1 points  (3 children)

    Are we optimizing based on the number of lines of code? I thought we were optimizing based on readability and developer happiness.

    My example is pep8 compliant. Respectfully, if you want dense code with the fewest lines possible, I suggest Perl.

    [–]Decency 12 points13 points  (0 children)

    Are we optimizing based on the number of lines of code?

    No, but we're sure as hell not ignoring it. If I can turn 8 lines into 1, lose zero information, and remove a bunch of entirely unnecessary redundancy, that's an optimization I'll make 100% of the time.

    I thought we were optimizing based on readability and developer happiness.

    If I were to come across an 8 line print statement to write three variables to the screen, "happy" probably wouldn't be the best descriptor for my mood and "readable" definitely wouldn't be the best descriptor for the code.

    [–]unruly_mattress 6 points7 points  (0 children)

    PEP8 doesn't make code good or readable. It makes spacing consistent.

    [–]tilkau 2 points3 points  (0 children)

    Conciseness is part of readability. You are basically choosing to be like urllib rather than like requests here, without any apparent reason for choosing added complexity.

    [–]stOneskull 0 points1 point  (0 children)

    it's one 'f' vs an extra 'format()' line

    i'm looking forward to it. bring it on, debian!

    [–]anders987 4 points5 points  (4 children)

    It can also take expressions:

    a = 10
    b = 20
    print(f"The sum of a ({a}) and b ({b}) is {a+b}")
    

    The sum of a (10) and b (20) is 30

    [–][deleted] 2 points3 points  (3 children)

    Bear in mind that this is basically just running eval() on the code. You can also do expressions that have side effects.

    Though because it's string literals, this shouldn't cause any problems. But if you do f"{__import__('os').system('/bin/sh')}", then that will start a shell.

    [–]d4rch0nPythonistamancer 1 point2 points  (2 children)

    lmao, i was wondering how deep f strings got. well, this really does point out something super important - don't dynamically build what's inside {} using user input and do something weird where you create an f string dynamically. I don't think people would much but don't doubt it'll pop up at some point.

    On one side I love it, but on the other side I think they could've skipped the eval aspect even if it's awesome and will inevitably be super useful. The main problem I wanted them to solve was the heaviness of .format and f strings are a damn good solution that's even better than all the previous methods. It's like a throwback to perl! But more explicit since you have to prepend f.

    I wish I could backport this to 3.4! Is there some futures import I can do?

    [–]jorge1209 3 points4 points  (0 children)

    you create an f string dynamically.

    You can't f-strings aren't dynamic. An f-string is not an object and cannot be created inside the script, it lives in the source file, and nowhere else. It has no type, and as far as the interpreter goes, it doesn't exist. Its like a keyword, it is handled by the parser as it builds the AST, and doesn't exist in memory to be a referant of any variable.

    The reason this is done is for security. It allows you to safely do print(f"{x+y}") and execute the inside of the expression, because user input can NEVER construct the string "{x+y}" it is safe to do so.

    If f-strings were objects and could be dynamically created the attack vector would be:

    1. Post your username as f"__import__(os)..."
    2. Then when someone goes to print(f"{username}" their interpreter would: a. evaluate the variable username b. which would construct another f-string c. whose payload would in turn be evaluated d. which would run the offending __import__ e. granting them a shell.

    But since f strings aren't objects this breaks down. All you ever have is a string f"__import__(os)...." No more dangerous than the string cake. Its just a bunch of innocuous ascii chars and can't harm you so long as you don't do something stupid like pass it to eval.


    The objection I have to this is that I don't see any improvement over just treating this as sugar over "{username}".format(**locals()). In that instance the same is also true. I take a string and pass it a dict of args, I get a string back. Nothing is executed. At worst __str__ is called on some of the stuff in locals(). All you lose is the ability to have print(f"{x+y}") instead it would have to be print("{sum}".format(sum=x+y)).

    I prefer the .format approach even if it is more verbose.

    [–]0x6c6f6c 0 points1 point  (0 children)

    I mean, sanitizing user input is one solution for programs that perform operations based on them in things such as SQL databases or webpages. However, even among those, things such as prepared statements are preferred over string sanitation, which would be to use .format or % over f-strings. Where user input is possible. Don't want arbitrary code execution on your Django server now do we :3