partial string formatting

前端 未结 21 1050
野的像风
野的像风 2020-11-28 04:30

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

F

21条回答
  •  眼角桃花
    2020-11-28 05:10

    Reading @Sam Bourne comment, I modified @SvenMarnach's code to work properly with coercion (like {a!s:>2s}) without writing a custom parser. The basic idea is not to convert to strings but concatenate missing keys with coercion tags.

    import string
    class MissingKey(object):
        def __init__(self, key):
            self.key = key
    
        def __str__(self):  # Supports {key!s}
            return MissingKeyStr("".join([self.key, "!s"]))
    
        def __repr__(self):  # Supports {key!r}
            return MissingKeyStr("".join([self.key, "!r"]))
    
        def __format__(self, spec): # Supports {key:spec}
            if spec:
                return "".join(["{", self.key, ":", spec, "}"])
            return "".join(["{", self.key, "}"])
    
        def __getitem__(self, i): # Supports {key[i]}
            return MissingKey("".join([self.key, "[", str(i), "]"]))
    
        def __getattr__(self, name): # Supports {key.name}
            return MissingKey("".join([self.key, ".", name]))
    
    
    class MissingKeyStr(MissingKey, str):
        def __init__(self, key):
            if isinstance(key, MissingKey):
                self.key = "".join([key.key, "!s"])
            else:
                self.key = key
    
    class SafeFormatter(string.Formatter):
        def __init__(self, default=lambda k: MissingKey(k)):
            self.default=default
    
        def get_value(self, key, args, kwds):
            if isinstance(key, str):
                return kwds.get(key, self.default(key))
            else:
                return super().get_value(key, args, kwds)
    

    Use (for example) like this

    SafeFormatter().format("{a:<5} {b:<10}", a=10)
    

    The following tests (inspired by tests from @norok2) check the output for the traditional format_map and a safe_format_map based on the class above in two cases: providing correct keywords or without them.

    def safe_format_map(text, source):
        return SafeFormatter().format(text, **source)
    
    test_texts = (
        '{a} ',             # simple nothing useful in source
        '{a:5d}',       # formatting
        '{a!s}',        # coercion
        '{a!s:>{a}s}',  # formatting and coercion
        '{a:0{a}d}',    # nesting
        '{d[x]}',       # indexing
        '{d.values}',   # member
    )
    
    source = dict(a=10,d=dict(x='FOO'))
    funcs = [safe_format_map,
             str.format_map
             #safe_format_alt  # Version based on parsing (See @norok2)
             ]
    n = 18
    for text in test_texts:
        # full_source = {**dict(b='---', f=dict(g='Oh yes!')), **source}
        # print('{:>{n}s} :   OK   : '.format('str.format_map', n=n) + text.format_map(full_source))
        print("Testing:", text)
        for func in funcs:
            try:
                print(f'{func.__name__:>{n}s} : OK\t\t\t: ' + func(text, dict()))
            except:
                print(f'{func.__name__:>{n}s} : FAILED')
    
            try:
                print(f'{func.__name__:>{n}s} : OK\t\t\t: ' + func(text, source))
            except:
                print(f'{func.__name__:>{n}s} : FAILED')
    

    Which outputs

    Testing: {a} 
       safe_format_map : OK         : {a} 
       safe_format_map : OK         : 10 
            format_map : FAILED
            format_map : OK         : 10 
    Testing: {a:5d}
       safe_format_map : OK         : {a:5d}
       safe_format_map : OK         :    10
            format_map : FAILED
            format_map : OK         :    10
    Testing: {a!s}
       safe_format_map : OK         : {a!s}
       safe_format_map : OK         : 10
            format_map : FAILED
            format_map : OK         : 10
    Testing: {a!s:>{a}s}
       safe_format_map : OK         : {a!s:>{a}s}
       safe_format_map : OK         :         10
            format_map : FAILED
            format_map : OK         :         10
    Testing: {a:0{a}d}
       safe_format_map : OK         : {a:0{a}d}
       safe_format_map : OK         : 0000000010
            format_map : FAILED
            format_map : OK         : 0000000010
    Testing: {d[x]}
       safe_format_map : OK         : {d[x]}
       safe_format_map : OK         : FOO
            format_map : FAILED
            format_map : OK         : FOO
    Testing: {d.values}
       safe_format_map : OK         : {d.values}
       safe_format_map : OK         : 
            format_map : FAILED
            format_map : OK         : 
    

提交回复
热议问题