partial string formatting

匿名 (未验证) 提交于 2019-12-03 00:50:01

问题:

Is it possible to do partial string formatting with the advanced string formatting methods, similar to the string template safe_substitute() function?

For example:

s = '{foo} {bar}' s.format(foo='FOO') #Problem: raises KeyError 'bar' 

回答1:

You can trick it into partial formatting by overwriting the mapping:

import string  class FormatDict(dict):     def __missing__(self, key):         return "{" + key + "}"  s = '{foo} {bar}' formatter = string.Formatter() mapping = FormatDict(foo='FOO') print(formatter.vformat(s, (), mapping)) 

printing

FOO {bar} 

Of course this basic implementation only works correctly for basic cases.



回答2:

If you know in what order you're formatting things:

s = '{foo} {{bar}}' 

Use it like this:

ss = s.format(foo='FOO')  print ss  >>> 'FOO {bar}'  print ss.format(bar='BAR') >>> 'FOO BAR' 

You can't specify foo and bar at the same time - you have to do it sequentially.



回答3:

This limitation of .format() - the inability to do partial substitutions - has been bugging me.

After evaluating writing a custom Formatter class as described in many answers here and even considering using third-party packages such as lazy_format, I discovered a much simpler inbuilt solution: Template strings

It provides similar functionality but also provides partial substitution thorough safe_substitute() method. The template strings need to have a $ prefix (which feels a bit weird - but the overall solution I think is better).

import string template = string.Template('${x} ${y}') try:   template.substitute({'x':1}) # raises KeyError except KeyError:   pass  # but the following raises no error partial_str = template.safe_substitute({'x':1}) # no error  # partial_str now contains a string with partial substitution partial_template = string.Template(partial_str) substituted_str = partial_template.safe_substitute({'y':2}) # no error print substituted_str # prints '12' 

Formed a convenience wrapper based on this:

class StringTemplate(object):     def __init__(self, template):         self.template = string.Template(template)         self.partial_substituted_str = None      def __repr__(self):         return self.template.safe_substitute()      def format(self, *args, **kws):         self.partial_substituted_str = self.template.safe_substitute(*args, **kws)         self.template = string.Template(self.partial_substituted_str)         return self.__repr__()   >>> s = StringTemplate('${x}${y}') >>> s '${x}${y}' >>> s.format(x=1) '1${y}' >>> s.format({'y':2}) '12' >>> print s 12 

Similarly a wrapper based on Sven's answer which uses the default string formatting:

class StringTemplate(object):     class FormatDict(dict):         def __missing__(self, key):             return "{" + key + "}"      def __init__(self, template):         self.substituted_str = template         self.formatter = string.Formatter()      def __repr__(self):         return self.substituted_str      def format(self, *args, **kwargs):         mapping = StringTemplate.FormatDict(*args, **kwargs)         self.substituted_str = self.formatter.vformat(self.substituted_str, (), mapping) 


回答4:

Not sure if this is ok as a quick workaround, but how about

s = '{foo} {bar}' s.format(foo='FOO', bar='{bar}') 

? :)



回答5:

If you define your own Formatter which overrides the get_value method, you could use that to map undefined field names to whatever you wanted:

http://docs.python.org/library/string.html#string.Formatter.get_value

For instance, you could map bar to "{bar}" if bar isn't in the kwargs.

However, that requires using the format() method of your Formatter object, not the string's format() method.



回答6:

>>> 'fd:{uid}:{{topic_id}}'.format(uid=123) 'fd:123:{topic_id}' 

Try this out.



回答7:

You could use the partial function from functools which is short, most readable and also describes best the coder's intention:

from functools import partial  s = partial("{foo} {bar}".format, foo="FOO") print s(bar="BAR") # FOO BAR 


回答8:

Thanks to Amber's comment, I came up with this:

import string  try:     # Python 3     from _string import formatter_field_name_split except ImportError:     formatter_field_name_split = str._formatter_field_name_split   class PartialFormatter(string.Formatter):     def get_field(self, field_name, args, kwargs):         try:             val = super(PartialFormatter, self).get_field(field_name, args, kwargs)         except (IndexError, KeyError, AttributeError):             first, _ = formatter_field_name_split(field_name)             val = '{' + field_name + '}', first         return val 


回答9:

For me this was good enough:

>>> ss = 'dfassf {} dfasfae efaef {} fds' >>> nn = ss.format('f1', '{}') >>> nn 'dfassf f1 dfasfae efaef {} fds' >>> n2 = nn.format('whoa') >>> n2 'dfassf f1 dfasfae efaef whoa fds' 


回答10:

There is one more way to achieve this i.e by using format and % to replace variables. For example:

>>> s = '{foo} %(bar)s' >>> s = s.format(foo='my_foo') >>> s 'my_foo %(bar)s' >>> s % {'bar': 'my_bar'} 'my_foo my_bar' 


回答11:

Assuming you won't use the string until it's completely filled out, you could do something like this class:

class IncrementalFormatting:     def __init__(self, string):         self._args = []         self._kwargs = {}         self._string = string      def add(self, *args, **kwargs):         self._args.extend(args)         self._kwargs.update(kwargs)      def get(self):         return self._string.format(*self._args, **self._kwargs) 

Example:

template = '#{a}:{}/{}?{c}' message = IncrementalFormatting(template) message.add('abc') message.add('xyz', a=24) message.add(c='lmno') assert message.get() == '#24:abc/xyz?lmno' 


回答12:

You could wrap it in a function that takes default arguments:

def print_foo_bar(foo='', bar=''):     s = '{foo} {bar}'     return s.format(foo=foo, bar=bar)  print_foo_bar(bar='BAR') # ' BAR' 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!