• 7

A PHP Error was encountered

Severity: Notice

Message: Undefined index: userid

Filename: views/question.php

Line Number: 191


File: /home/prodcxja/public_html/questions/application/views/question.php
Line: 191
Function: _error_handler

File: /home/prodcxja/public_html/questions/application/controllers/Questions.php
Line: 433
Function: view

File: /home/prodcxja/public_html/questions/index.php
Line: 315
Function: require_once

name Punditsdkoslkdosdkoskdo

partial string formatting

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'

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.

  • 128
Reply Report
    • What's the point of this? If I specify both foo and bar: s.format(foo='FOO',bar='BAR') then I still got 'FOO {bar}', no matter what. Could you clarify it?
      • 1
    • That you can't fill out both at once is annoying. This is useful when, for whatever reason, you have to format your string in stages and you know the order of those stages.
    • This is super useful when populating part of a string in one part of your code, but leaving a placeholder to be populated later in another part of your code.

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

from functools import partial

s = partial("{foo} {bar}".format, foo="FOO")
print s(bar="BAR")
  • 97
Reply Report
      • 1
    • Not only the shortest and most readable solution, but also describes the coder's intention. Python3 version: python from functool import partial s = "{foo} {bar}".format s_foo = partial(s, foo="FOO") print(s_foo(bar="BAR")) # FOO BAR print(s(foo="FOO", bar="BAR")) # FOO BAR
      • 1
    • @ypercube?? Well, I am not sure this is exactly what most people are looking for. partial() is not gonna help me if I need to do some processing with the partially formatted string (that is "FOO {bar}").
    • This is better for the case when you're operating on input that you don't 100% control. Imagine: "{foo} {{bar}}".format(foo="{bar}").format(bar="123") from the other examples. I would expect "{bar} 123" but they output "123 123".

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))


FOO {bar}

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

  • 58
Reply Report
      • 2
    • @norok2 It's an answer to a question asked in a comment, so I put the reply in a comment. The original question did not really include that requirement, and I generally still think that it's a bit weird to try to partial format a string.
    • I understand saying that "the most basic implementation only works correctly for the basic cases" but is there a way to expand this to even just not delete the format spec?
    • @TadhgMcDonald-Jensen: Yes, there is a way. Instead of returning a string in __missing__(), return an instance of a custom class overriding __format__() in a way to return the original placeholder including the format spec. Proof of concept: ideone.com/xykV7R
      • 2
    • @SvenMarnach why is your proof of concept not in the body of your answer? That's a bit elusive. Are there any known caveats preventing you from promoting it?

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}')
  template.substitute({'x':1}) # raises KeyError
except KeyError:

# 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
>>> s.format(x=1)
>>> s.format({'y':2})
>>> print s

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)
  • 50
Reply Report

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:


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.

  • 11
Reply Report
>>> 'fd:{uid}:{{topic_id}}'.format(uid=123)

Try this out.

  • 11
Reply Report
      • 1
    • {{ and }} is a way of escaping the formatting marks, so format() does not perform substitution and replaces {{ and }} with { and }, respectively.
      • 1
    • The issue of this solution is that the double {{ }} only works for one format, if you need to apply more you would need to add more {}. ex. 'fd:{uid}:{{topic_id}}'.format(uid=123).format(a=1) will return error since second format is not providing the topic_id value.

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

import string

    # 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):
            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
  • 7
Reply Report

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'
  • 3
Reply Report

All the solutions I've found seemed to have issues with more advanced spec or conversion options. @SvenMarnach's FormatPlaceholder is wonderfully clever but it doesn't work properly with coercion (e.g. {a!s:>2s}) because it calls the __str__ method (in this example) instead of __format__ and you lose any additional formatting.

Here's what I ended up with and some of it's key features:

sformat('The {} is {}', 'answer')
'The answer is {}'

sformat('The answer to {question!r} is {answer:0.2f}', answer=42)
'The answer to {question!r} is 42.00'

sformat('The {} to {} is {:0.{p}f}', 'answer', 'everything', p=4)
'The answer to everything is {:0.4f}'
  • provides similar interface as str.format (not just a mapping)
  • supports more complex formatting options:
    • coercion {k!s} {!r}
    • nesting {k:>{size}}
    • getattr {k.foo}
    • getitem {k[0]}
    • coercion+formatting {k!s:>{size}}
import string

class SparseFormatter(string.Formatter):
    A modified string formatter that handles a sparse set of format

    # re-implemented this method for python2/3 compatibility
    def vformat(self, format_string, args, kwargs):
        used_args = set()
        result, _ = self._vformat(format_string, args, kwargs, used_args, 2)
        self.check_unused_args(used_args, args, kwargs)
        return result

    def _vformat(self, format_string, args, kwargs, used_args, recursion_depth,
        if recursion_depth < 0:
            raise ValueError('Max string recursion exceeded')
        result = []
        for literal_text, field_name, format_spec, conversion in \

            orig_field_name = field_name

            # output the literal text
            if literal_text:

            # if there's a field, output it
            if field_name is not None:
                # this is some markup, find the object and do
                #  the formatting

                # handle arg indexing when empty field_names are given.
                if field_name == '':
                    if auto_arg_index is False:
                        raise ValueError('cannot switch from manual field '
                                         'specification to automatic field '
                    field_name = str(auto_arg_index)
                    auto_arg_index += 1
                elif field_name.isdigit():
                    if auto_arg_index:
                        raise ValueError('cannot switch from manual field '
                                         'specification to automatic field '
                    # disable auto arg incrementing, if it gets
                    # used later on, then an exception will be raised
                    auto_arg_index = False

                # given the field_name, find the object it references
                #  and the argument it came from
                    obj, arg_used = self.get_field(field_name, args, kwargs)
                except (IndexError, KeyError):
                    # catch issues with both arg indexing and kwarg key errors
                    obj = orig_field_name
                    if conversion:
                        obj += '!{}'.format(conversion)
                    if format_spec:
                        format_spec, auto_arg_index = self._vformat(
                            format_spec, args, kwargs, used_args,
                            recursion_depth, auto_arg_index=auto_arg_index)
                        obj += ':{}'.format(format_spec)
                    result.append('{' + obj + '}')

                    # do any conversion on the resulting object
                    obj = self.convert_field(obj, conversion)

                    # expand the format spec, if needed
                    format_spec, auto_arg_index = self._vformat(
                        format_spec, args, kwargs,
                        used_args, recursion_depth-1,

                    # format the object and append to the result
                    result.append(self.format_field(obj, format_spec))

        return ''.join(result), auto_arg_index

def sformat(s, *args, **kwargs):
    # type: (str, *Any, **Any) -> str
    Sparse format a string.

    s : str
    args : *Any
    kwargs : **Any

    >>> sformat('The {} is {}', 'answer')
    'The answer is {}'

    >>> sformat('The answer to {question!r} is {answer:0.2f}', answer=42)
    'The answer to {question!r} is 42.00'

    >>> sformat('The {} to {} is {:0.{p}f}', 'answer', 'everything', p=4)
    'The answer to everything is {:0.4f}'

    return SparseFormatter().format(s, *args, **kwargs)

I discovered the issues with the various implementations after writing some tests on how I wanted this method to behave. They're below if anyone finds them insightful.

import pytest

def test_auto_indexing():
    # test basic arg auto-indexing
    assert sformat('{}{}', 4, 2) == '42'
    assert sformat('{}{} {}', 4, 2) == '42 {}'

def test_manual_indexing():
    # test basic arg indexing
    assert sformat('{0}{1} is not {1} or {0}', 4, 2) == '42 is not 2 or 4'
    assert sformat('{0}{1} is {3} {1} or {0}', 4, 2) == '42 is {3} 2 or 4'

def test_mixing_manualauto_fails():
    # test mixing manual and auto args raises
    with pytest.raises(ValueError):
        assert sformat('{!r} is {0}{1}', 4, 2)

def test_kwargs():
    # test basic kwarg
    assert sformat('{base}{n}', base=4, n=2) == '42'
    assert sformat('{base}{n}', base=4, n=2, extra='foo') == '42'
    assert sformat('{base}{n} {key}', base=4, n=2) == '42 {key}'

def test_args_and_kwargs():
    # test mixing args/kwargs with leftovers
    assert sformat('{}{k} {v}', 4, k=2) == '42 {v}'

    # test mixing with leftovers
    r = sformat('{}{} is the {k} to {!r}', 4, 2, k='answer')
    assert r == '42 is the answer to {!r}'

def test_coercion():
    # test coercion is preserved for skipped elements
    assert sformat('{!r} {k!r}', '42') == "'42' {k!r}"

def test_nesting():
    # test nesting works with or with out parent keys
    assert sformat('{k:>{size}}', k=42, size=3) == ' 42'
    assert sformat('{k:>{size}}', size=3) == '{k:>3}'

    ('s', 'expected'),
        ('{a} {b}', '1 2.0'),
        ('{z} {y}', '{z} {y}'),
        ('{a} {a:2d} {a:04d} {y:2d} {z:04d}', '1  1 0001 {y:2d} {z:04d}'),
        ('{a!s} {z!s} {d!r}', '1 {z!s} {\'k\': \'v\'}'),
        ('{a!s:>2s} {z!s:>2s}', ' 1 {z!s:>2s}'),
        ('{a!s:>{a}s} {z!s:>{z}s}', '1 {z!s:>{z}s}'),
        ('{a.imag} {z.y}', '0 {z.y}'),
        ('{e[0]:03d} {z[0]:03d}', '042 {z[0]:03d}'),
def test_sformat(s, expected):
    # test a bunch of random stuff
    data = dict(
        d={'k': 'v'},
    assert expected == sformat(s, **data)
  • 3
Reply Report
    • I added an answer that is similar to @SvenMarnach code but which that handles coercion correctly for your tests.

My suggestion would be the following (tested with Python3.6):

class Lazymap(object):
       def __init__(self, **kwargs):
           self.dict = kwargs

       def __getitem__(self, key):
           return self.dict.get(key, "".join(["{", key, "}"]))

s = '{foo} {bar}'

# >>> '{foo} FOO'

# >>> '{foo} BAR'

s.format_map(Lazymap(bar="BAR", foo="FOO", baz="BAZ"))
# >>> 'FOO BAR'

Update: An even more elegant way (subclassing dict and overloading __missing__(self, key)) is shown here: https://stackoverflow.com/a/17215533/333403

  • 1
Reply Report

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):

    def get(self):
        return self._string.format(*self._args, **self._kwargs)


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

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'
  • 0
Reply Report

A very ugly but the simplest solution for me is to just do:

tmpl = '{foo}, {bar}'
tmpl.replace('{bar}', 'BAR')
Out[3]: '{foo}, BAR'

This way you still can use tmpl as regular template and perform partial formatting only when needed. I find this problem too trivial to use a overkilling solution like Mohan Raj's.

  • 0
Reply Report

After testing the most promising solutions from here and there, I realized that none of them really met the following requirements:

  1. strictly adhere to the syntax recognized by str.format_map() for the template;
  2. being able to retain complex formatting, i.e. fully supporting the Format Mini-Language

So, I wrote my own solution, which satisfies the above requirements. (EDIT: now the version by @SvenMarnach -- as reported in this answer -- seems to handle the corner cases I needed).

Basically, I ended up parsing the template string, finding matching nested {.*?} groups (using a find_all() helper function) and building the formatted string progressively and directly using str.format_map() while catching any potential KeyError.

def find_all(
    Find all occurrencies of the pattern in the text.

        text (str|bytes|bytearray): The input text.
        pattern (str|bytes|bytearray): The pattern to find.
        overlap (bool): Detect overlapping patterns.

        position (int): The position of the next finding.
    len_text = len(text)
    offset = 1 if overlap else (len(pattern) or 1)
    i = 0
    while i < len_text:
        i = text.find(pattern, i)
        if i >= 0:
            yield i
            i += offset
def matching_delimiters(
    Find matching delimiters in a sequence.

    The delimiters are matched according to nesting level.

        text (str|bytes|bytearray): The input text.
        l_delim (str|bytes|bytearray): The left delimiter.
        r_delim (str|bytes|bytearray): The right delimiter.
        including (bool): Include delimeters.

        result (tuple[int]): The matching delimiters.
    l_offset = len(l_delim) if including else 0
    r_offset = len(r_delim) if including else 0
    stack = []

    l_tokens = set(find_all(text, l_delim))
    r_tokens = set(find_all(text, r_delim))
    positions = l_tokens.union(r_tokens)
    for pos in sorted(positions):
        if pos in l_tokens:
            stack.append(pos + 1)
        elif pos in r_tokens:
            if len(stack) > 0:
                prev = stack.pop()
                yield (prev - l_offset, pos + r_offset, len(stack))
                raise ValueError(
                    'Found `{}` unmatched right token(s) `{}` (position: {}).'
                        .format(len(r_tokens) - len(l_tokens), r_delim, pos))
    if len(stack) > 0:
        raise ValueError(
            'Found `{}` unmatched left token(s) `{}` (position: {}).'
                len(l_tokens) - len(r_tokens), l_delim, stack.pop() - 1))
def safe_format_map(
    Perform safe string formatting from a mapping source.

    If a value is missing from source, this is simply ignored, and no
    `KeyError` is raised.

        text (str): Text to format.
        source (Mapping|None): The mapping to use as source.
            If None, uses caller's `vars()`.

        result (str): The formatted text.
    stack = []
    for i, j, depth in matching_delimiters(text, '{', '}'):
        if depth == 0:
                replacing = text[i:j].format_map(source)
            except KeyError:
                stack.append((i, j, replacing))
    result = ''
    i, j = len(text), 0
    while len(stack) > 0:
        last_i = i
        i, j, replacing = stack.pop()
        result = replacing + text[j:last_i] + result
    if i > 0:
        result = text[0:i] + result
    return result

(This code is also available in FlyingCircus -- DISCLAIMER: I am the main author of it.)

The usage for this code would be:

print(safe_format_map('{a} {b} {c}', dict(a=-A-)))
# -A- {b} {c}

Let's compare this to the my favourite solution (by @SvenMarnach who kindly shared his code here and there):

import string

class FormatPlaceholder:
    def __init__(self, key):
        self.key = key
    def __format__(self, spec):
        result = self.key
        if spec:
            result += ":" + spec
        return "{" + result + "}"
    def __getitem__(self, index):
        self.key = "{}[{}]".format(self.key, index)
        return self
    def __getattr__(self, attr):
        self.key = "{}.{}".format(self.key, attr)
        return self

class FormatDict(dict):
    def __missing__(self, key):
        return FormatPlaceholder(key)

def safe_format_alt(text, source):
    formatter = string.Formatter()
    return formatter.vformat(text, (), FormatDict(source))

Here are a couple of tests:

test_texts = (
    '{b} {f}',  # simple nothing useful in source
    '{a} {b}',  # simple
    '{a} {b} {c:5d}',  # formatting
    '{a} {b} {c!s}',  # coercion
    '{a} {b} {c!s:>{a}s}',  # formatting and coercion
    '{a} {b} {c:0{a}d}',  # nesting
    '{a} {b} {d[x]}',  # dicts (existing in source)
    '{a} {b} {e.index}',  # class (existing in source)
    '{a} {b} {f[g]}',  # dict (not existing in source)
    '{a} {b} {f.values}',  # class (not existing in source)

source = dict(a=4, c=101, d=dict(x='FOO'), e=[])

and the code to make it running:

funcs = safe_format_map, safe_format_alt

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))
    for func in funcs:
            print(f'{func.__name__:>{n}s} :   OK   : ' + func(text, source))
            print(f'{func.__name__:>{n}s} : FAILED : {text}')

resulting in:

    str.format_map :   OK   : --- {'g': 'Oh yes!'}
   safe_format_map :   OK   : {b} {f}
   safe_format_alt :   OK   : {b} {f}
    str.format_map :   OK   : 4 ---
   safe_format_map :   OK   : 4 {b}
   safe_format_alt :   OK   : 4 {b}
    str.format_map :   OK   : 4 ---   101
   safe_format_map :   OK   : 4 {b}   101
   safe_format_alt :   OK   : 4 {b}   101
    str.format_map :   OK   : 4 --- 101
   safe_format_map :   OK   : 4 {b} 101
   safe_format_alt :   OK   : 4 {b} 101
    str.format_map :   OK   : 4 ---  101
   safe_format_map :   OK   : 4 {b}  101
   safe_format_alt :   OK   : 4 {b}  101
    str.format_map :   OK   : 4 --- 0101
   safe_format_map :   OK   : 4 {b} 0101
   safe_format_alt :   OK   : 4 {b} 0101
    str.format_map :   OK   : 4 --- FOO
   safe_format_map :   OK   : 4 {b} FOO
   safe_format_alt :   OK   : 4 {b} FOO
    str.format_map :   OK   : 4 --- <built-in method index of list object at 0x7f7a485666c8>
   safe_format_map :   OK   : 4 {b} <built-in method index of list object at 0x7f7a485666c8>
   safe_format_alt :   OK   : 4 {b} <built-in method index of list object at 0x7f7a485666c8>
    str.format_map :   OK   : 4 --- Oh yes!
   safe_format_map :   OK   : 4 {b} {f[g]}
   safe_format_alt :   OK   : 4 {b} {f[g]}
    str.format_map :   OK   : 4 --- <built-in method values of dict object at 0x7f7a485da090>
   safe_format_map :   OK   : 4 {b} {f.values}
   safe_format_alt :   OK   : 4 {b} {f.values}

as you can see, the updated version now seems to handle well the corner cases where the earlier version used to fail.

Timewise, they are within approx. 50% of each other, depending on the actual text to format (and likely the actual source), but safe_format_map() seems to have an edge in most of the tests I performed (whatever they mean, of course):

for text in test_texts:
    print(f'  {text}')
    %timeit safe_format(text * 1000, source)
    %timeit safe_format_alt(text * 1000, source)
  {b} {f}
3.93 ms ± 153 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
6.35 ms ± 51.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b}
4.37 ms ± 57.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
5.2 ms ± 159 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b} {c:5d}
7.15 ms ± 91.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
7.76 ms ± 69.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b} {c!s}
7.04 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
7.56 ms ± 161 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b} {c!s:>{a}s}
8.91 ms ± 113 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
10.5 ms ± 181 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b} {c:0{a}d}
8.84 ms ± 147 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
10.2 ms ± 202 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b} {d[x]}
7.01 ms ± 197 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
7.35 ms ± 106 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b} {e.index}
11 ms ± 68.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
8.78 ms ± 405 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b} {f[g]}
6.55 ms ± 88.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
9.12 ms ± 159 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  {a} {b} {f.values}
6.61 ms ± 55.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
9.92 ms ± 98.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  • 0
Reply Report
    • @SvenMarnach The official docs explicitly tell field_name ::= arg_name ("." attribute_name | "[" element_index "]")* and both str.format() and str.format_map() understand it. I'd say there is enough evidence for this being a valid format string.
      • 1
    • Can you give an example of using str.format() with a non-integer index in square brackets? I can only make integer indexes work.
      • 2
    • Ah, I see. I was assuming this gets interpreted like a[b] in Python code, but it actually is a["b"] Thanks!

Trending Tags