Python: uses "..% (var) s .."% locals () good practice?

I discovered this pattern (or anti-pattern), and I am very pleased with it.

I feel that he is very agile:

def example(): age = ... name = ... print "hello %(name)s you are %(age)s years old" % locals() 

Sometimes I use my cousin:

 def example2(obj): print "The file at %(path)s has %(length)s bytes" % obj.__dict__ 

I don’t need to create artificial tuples and count parameters and keep matching% s positions inside the tuple.

You like? Do / Do you use it? Yes / No, please explain.

+68
python string design-patterns anti-patterns
Oct 11 '09 at 11:27
source share
7 answers

This is normal for small applications and supposedly “one-time” scripts, especially with the vars improvement mentioned in @ kaizer.se and the .format version mentioned by @RedGlyph.

However, for large, long-life applications and many proponents, this practice can lead to maintenance headaches, and I think when @ S.Lott's answer comes. Let me explain some of the issues raised, as they may not be obvious to those who do not have scars from developing and supporting large applications (or reusable components for such animals).

In a “serious” application, you won’t have hard-coded formatted string code - or, if you had, it would be in some form, for example _('Hello {name}.') , Where _ comes from gettext or similar i18n frameworks / L10n. The fact is that such an application (or reusable modules that can be used in such applications) must support internationalization (AKA i18n) and localization (AKA L10n): you want your application to be able to emit "Hello Paul" in certain countries and cultures, "Hola Paul" in some others, "Ciao Paul" in others, and so on. Thus, the format string is more or less automatically replaced by another at runtime, depending on the current localization settings; instead of being hardcoded, it lives in some kind of database. In all senses and purposes, imagine that a format string is always a variable, not a string literal.

So, you have essentially

 formatstring.format(**locals()) 

and you cannot trivially check which local names will use formatting. You will need to open and examine the L10N database, determine the format strings that will be used here in different settings, check them all.

Thus, in practice, you do not know what local names will be used, which terribly interferes with maintaining the function. You do not dare to rename or delete any local variable, as this can greatly disrupt the user interface for users with some (you) incomprehensible combination of language, language and preferences.

If you have excellent integration / regression testing, a breakdown will be detected before the beta, but QA will yell at you, and the release will be delayed ... and to be honest, aiming at 100% coverage with unit tests is reasonable, it really is not with integration tests, as soon as you consider the combinatorial burst of settings [[for L10N and for many other reasons]] and the supported versions of all the dependencies. Thus, you simply do not carelessly circumvent the risk of breakdown, because "they will be caught in QA" (if you do this, you may not live in an environment that develops large applications or reusable components for a long time ;-).

Thus, in practice, you will never delete the local variable "name", even if People Experience users have long switched this greeting to the more appropriate "Welcome, Dread Overlord!" (and accordingly L10n'ed version). That's because you went for locals() ...

So, you accumulate due to the fact that you have the ability to save and edit your code, and perhaps this local variable "name" exists only because it was extracted from a database or the like, therefore saving it (or some others local) around is not just cool, it also reduces your productivity. The convenience of surface locals() worth what ?)

But wait, worse! Among many useful services, the lint -like program (for example,

Compare this to the "explicit is better than implicit" alternative ...:

 blah.format(name=name) 

There is nothing of concern for maintenance, performance, and am-I-hampering-lint; bliss! You immediately understood to everyone who concerned (lint included ;-) exactly what local variables are used, and exactly for what purpose.

I could go on, but I think this post is already quite long; -).

So, summing up: " γνῶθι σεαυτόν !" Hmm, I mean, "know yourself!". And "myself" I actually mean "the purpose and scope of your code." If this is a 1-off-or-thereabout thingy, there will never be i18n'd and L10n'd, service in the future is unlikely to be needed, will never be reused in a wider context, etc. Etc., Then go and use locals() for its small but neat convenience; if you know differently, or even if you are not completely sure, make a mistake on the side of caution and make everything more obvious - experience slight inconvenience in writing exactly what you are going to, and enjoy all the benefits arising from this.

By the way, this is just one example where Python seeks to support both “small, one-time, research, and interactive” programming (allowing and supporting dangerous amenities that go far beyond locals() ). import * , eval , exec and several other ways to simplify the namespace and risks for ease of maintenance), as well as "large, reusable, enterprise" applications and components. This can do a pretty good job for both, but only if you "know yourself" and avoid using the "convenient" parts, unless you are absolutely sure that you can actually buy them. Most often, this is a key consideration: "What does this do with my namespaces and the awareness of their formation and use by the compiler, readers and readers, people, etc.?".

Remember: "Namespaces are one great idea - let more of them!" how Zen Python completes ... but Python, as a "language for agreeing adults," allows you to define the boundaries of what this implies, as a consequence of your development environment, goals and practice. Use this power responsibly! -)

+84
Oct 11 '09 at 17:14
source share

I think this is a great template because you use the built-in functions to reduce the code you need to write. I personally find this pretty Pythonic.

I never write code that I don’t need to write - less code is better than more code, and this practice of using locals() , for example, allows me to write less code and is also very easy to read and understand.

+10
Oct 11 '09 at 11:28
source share

Never in a million years. It's unclear what the context for formatting is: locals can include almost any variable. self.__dict__ not so vague. It is absolutely terrible for future developers to scratch their heads over what is local and what is not local.

This is a deliberate secret. Why are saddles your organization with future headaches in maintenance?

+10
Oct 11 '09 at 13:22
source share

As for the cousin, instead of obj.__dict__ , it looks much better when formatting a new line:

 def example2(obj): print "The file at {o.path} has {o.length} bytes".format(o=obj) 

I use this for repr methods, for example.

 def __repr__(self): return "{s.time}/{s.place}/{s.warning}".format(s=self) 
+10
May 24 '11 at 6:32
source share

"%(name)s" % <dictionary> or even better, "{name}".format(<parameters>) have advantages

  • is more readable than "% 0s"
  • independent of the order of the arguments
  • it is not necessary to use all arguments in a string

I would like to use str.format () since this should be a way to do this in Python 3 (as per PEP 3101 ) and is already available since 2.6. However, with locals() you will need to do this:

 print("hello {name} you are {age} years old".format(**locals())) 
+8
Oct 11 '09 at 11:54
source share

Using the built-in vars([object]) ( documentation ) may make the second look better for you:

 def example2(obj): print "The file at %(path)s has %(length)s bytes" % vars(obj) 

The effect, of course, is the same.

+6
11 Oct '09 at 13:15
source share

Now there is an official way to do this, starting with Python 3.6.0: formatted string literals .

It works as follows:

 f'normal string text {local_variable_name}' 

eg. instead of them:

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

just do this:

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

Here is an 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' 

Link:

0
Jun 08 '17 at 17:38 on
source share



All Articles