Is a string formatter that pulls variables from its calling scope bad practice?

后端 未结 4 1568
北恋
北恋 2020-11-30 14:04

I have some code that does an awful lot of string formatting, Often, I end up with code along the lines of:

\"...\".format(x=x, y=y, z=z, foo=foo, ...)


        
相关标签:
4条回答
  • 2020-11-30 14:26

    In the inspect module, currentframe is defined like this:

    if hasattr(sys, '_getframe'):
        currentframe = sys._getframe
    else:
        currentframe = lambda _=None: None
    

    So unless sys has a _getframe attribute, the interpolate function will not work.

    The docs for sys._getframe say:

    CPython implementation detail: This function should be used for internal and specialized purposes only. It is not guaranteed to exist in all implementations of Python.


    Writing

    "{x}, {y}, {z}".format(**vars())
    

    in the function body is not that much longer than

    interpolate("{x}, {y}, {z}")
    

    and your code will be more portable.

    0 讨论(0)
  • 2020-11-30 14:27

    Update: Python 3.6 has this feature (a more powerful variant) builtin:

    x, y, z = range(3)
    print(f"{x} {y + z}")
    # -> 0 3
    

    See PEP 0498 -- Literal String Interpolation


    It[manual solution] leads to somewhat surprising behaviour with nested functions:

    from callerscope import format
    
    def outer():
        def inner():
            nonlocal a
            try:
                print(format("{a} {b}"))
            except KeyError as e:
                assert e.args[0] == 'b'
            else:
                assert 0
    
        def inner_read_b():
            nonlocal a
            print(b) # read `b` from outer()
            try:
                print(format("{a} {b}"))
            except KeyError as e:
                assert 0
        a, b = "ab"
        inner()
        inner_read_b()
    

    Note: the same call succeeds or fails depending on whether a variable is mentioned somewhere above or below it.

    Where callerscope is:

    import inspect
    from collections import ChainMap
    from string import Formatter
    
    def format(format_string, *args, _format=Formatter().vformat, **kwargs):
        caller_locals = inspect.currentframe().f_back.f_locals
        return _format(format_string, args, ChainMap(kwargs, caller_locals))
    
    0 讨论(0)
  • 2020-11-30 14:29

    A simpler and safer approach would be the code below. inspect.currentframe isn't available on all implementation of python so your code would break when it isn't. Under jython, ironpython, or pypy it might not be available because it seems to be a cpython thing. This makes your code less portable.

    print "{x}, {y}".format(**vars())
    

    this technique is actually described in the Python tutorial's Input and Output chapter

    This could also be done by passing the table as keyword arguments with the ‘**’ notation. This is particularly useful in combination with the new built-in vars() function, which returns a dictionary containing all local variables.

    also in the python docs for inspect.currentframe

    CPython implementation detail: This function relies on Python stack frame support in the interpreter, which isn’t guaranteed to exist in all implementations of Python. If running in an implementation without Python stack frame support this function returns None.

    0 讨论(0)
  • 2020-11-30 14:39

    The good old mailman has a function _ that does exactly this thing:

    def _(s):
        if s == '':
            return s
        assert s
        # Do translation of the given string into the current language, and do
        # Ping-string interpolation into the resulting string.
        #
        # This lets you write something like:
        #
        #     now = time.ctime(time.time())
        #     print _('The current time is: %(now)s')
        #
        # and have it Just Work.  Note that the lookup order for keys in the
        # original string is 1) locals dictionary, 2) globals dictionary.
        #
        # First, get the frame of the caller
        frame = sys._getframe(1)
        # A `safe' dictionary is used so we won't get an exception if there's a
        # missing key in the dictionary.
        dict = SafeDict(frame.f_globals.copy())
        dict.update(frame.f_locals)
        # Translate the string, then interpolate into it.
        return _translation.gettext(s) % dict
    

    So if Barry Warsaw can do that, why can't we?

    0 讨论(0)
提交回复
热议问题