Using locals() and format() method for strings: are there any caveats?
Asked Answered
L

3

57

Are there any disadvantages, caveats or bad practice warnings about using the following pattern?

def buildString(user, name = 'john', age=22):
    userId = user.getUserId()
    return "Name: {name}, age: {age}, userid:{userId}".format(**locals())

I had a very repetitive string generation code to write and was tempted to use this, but something about using locals() makes me uncomfortable. Is there any danger of unexpected behavior in this?

Edit: context

I found myself constantly writing stuff like:

"{name} {age} {userId} {etc}...".format(name=name, age=age, userId=userId, etc=etc)
Lipolysis answered 1/8, 2012 at 17:59 Comment(2)
I don't see anything horribly wrong with it ...Hooker
Closely related to https://mcmap.net/q/273787/-python-is-using-quot-var-s-quot-locals-a-good-practice/125507Retinoscope
L
41

There is now an official way to do this, as of Python 3.6.0: formatted string literals.

It works like this:

f'normal string text {local_variable_name}'

E.g. instead of these:

"hello %(name) you are %(age) years old" % locals()
"hello {name} you are {age} years old".format(**locals())
"hello {name} you are {age} years old".format(name=name, age=age)

just do this:

f"hello {name} you are {age} years old"

Here's the official example:

>>> name = "Fred"
>>> f"He said his name is {name}."
'He said his name is Fred.'
>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: {value:{width}.{precision}}"  # nested fields
'result:      12.35'

Reference:

Lafontaine answered 8/6, 2017 at 17:48 Comment(0)
K
30

If the format string is not user-supplied, this usage is okay.

format is preferred over using the old % for string substitution.
locals is built-in to Python and its behavior will be reliable.

I think locals does exactly what you need.
Just don't modify the dictionary from locals and I would say you have a pretty good solution.

If the format string is user-supplied, you are susceptible to injection attacks of all sorts of badness.

Kore answered 1/8, 2012 at 18:7 Comment(4)
Shouldn't there be a caveat that the string never be user-supplied? That could open up access to the contents of every local variable.Mclean
Straight shooting @BobStein-VisiBone, I have updated my answer.Kore
There appears to be a very small performance overhead using locals() rather than just the variables you need, since you have an extra function call and you're constructing a larger dictionary. It's probably negligible for most applications though: I'm seeing differences of about 2% in a quick test case.Walkon
Instead of user supplied one could say it is internally generated or something, this would apply then here tooLewendal
A
2

Pre Python 3.6 answer

This is very old, but if you find yourself using .format the one caveat I have encountered with passing in **locals is that if you don't have that variable defined anywhere, it will break. Explicitly stating what variables are passed in will avoid this in most modern IDEs.

foo = "bar"
"{foo} and {baz} are pair programming".format(**locals())
<exception occurs>
Alvord answered 29/9, 2017 at 15:53 Comment(2)
Use this: f"{foo} and {baz} are pair programming" And you will get a NameError. But in this case the IDE can detect it. The original solution should be avoided. Just use everywhere f-strings and enforce the use of Python 3.6+ Additionally you'll get syntax for asyncio for free.Hockey
@Hockey I think I found a case where .format is still useful: if the variables are defined after the string to format. s = """{foo} is not {bar}"""; foo = "A"; bar = "B"; f"{s}".format(**locals())Octosyllable

© 2022 - 2024 — McMap. All rights reserved.