[Python-ideas] Draft PEP on string interpolation
Eric V. Smith
eric at trueblade.com
Wed Aug 26 14:56:51 CEST 2015
On 8/25/2015 10:20 PM, Ron Adam wrote:
> On 08/24/2015 09:42 PM, Eric V. Smith wrote:
>>> On Aug 24, 2015, at 10:23 PM, Ron
>>> Adam<ron3200 at gmail.com> wrote:
>>>>
>>>> On 08/24/2015 06:45 PM, Mike Miller wrote:
>>>>>>>> - How problematic will it be that an e-string pins all
>>>>>>>> the interpolated objects in memory for its lifetime?
>>>>>>
>>>>>> It will be an object holding a raw template string, and a
>>>>>> number of variables. In normal usage I don't suspect it to be
>>>>>> a problem.
>>>>
>>>> If an objects __str__ method could have an optional fmt='spec'
>>>> argument, then an estring, could just hold strings, and not the
>>>> object references. That also prevent surprises if the object is
>>>> mutated between the time it's estring is created and when the
>>>> estring is used as a string. For that matter it prevents an
>>>> estring from printing one way at one time, and another at another
>>>> time.
>>>>
>>>> I don't know if the fomatting can be split like this... Where an
>>>> object is formatted to a string representation, and then that is
>>>> formatted to a field specification. The later being things like
>>>> width, fill, right, center, and left. These are independent of
>>>> the object and belong to the string. Things like nubmer of
>>>> places and sign or to use leading or trailing zeros is part of
>>>> the object being converted to a string.
>
>> It's not possible. For examples, look at all of the number format
>> options. How would you implement hex conversions? Or datetime %A?
>
> I'm not sure which part you are referring to.. But I think adding an
> optional argument to __str__ methods is probably out.
The part that's not possible is to have the format_spec always be
interpreted on a string ojbect, even if the format_spec refers to a
different type (such as datetime).
> As to splitting the format spec, I think it would be possible, but It
> may not be needed.
>
> I still think early evaluation is a must here. The issue I have with
> the late evaluation is shown in your current example of logging. If the
> time which may be from an actual time() function rather than a fixed
> time is not evaluated until the logged list is printed at the end of the
> run, all the times will be set to when it's printed rather than when the
> logged even happened.
There are two things being evaluated: the expressions (the things inside
the {}'s), and the value of the i-string (or whatever it's called here,
I've lost track). The expressions would be evaluated immediately, when
the i-string is created. This is identical to what would happen if,
instead of being in an i-string, the expressions were written in Python
code. The value of the i-string would be evaluated later, such as when
str() or log() or whatever evaluated the contents of the string.
This is what my example on bitbucket does. See i.__init__ for eval(),
where the expressions are evaluated. Then later, i.join() actually
evaluates the content of the string.
Note that evaluating the i-string need not result in a string as the
result. See the regex example. The 'i' class needs better support for
this, but it's doable. Adding that is on my list of things to do, once I
have a better API thought out.
> Another similar reason is the evaluated expression is sensitive to what
> object is in the name at the time it is evaluated. If it's evaluated
> later, the object from the name look up may be something entirely
> unexpected because that name may have been reused during each iteration
> of a loop. So all the logged entries that refer to that name will give
> the last value rather than the value at the time the event was logged.
Sure. Currently:
logging.info('the time is %s', datetime.datetime.now())
Evaluates the current time immediately, but builds up the string later.
That's equivalent to what this would do in my bitbucket log.py example:
msg = i("the time is {datetime.datetime.now()}")
log.log(msg)
Also, see test_i in simple.py, again on bitbucket. It shows that
changing the values after an i-string is created has no effect on the
contents of the i-string. This would be different if the values were
mutable, of course. I'll add a test for that to show what I mean.
I think your example below is a functional subset of what I have on
bitbucket. The only real distinction is that I can do substitutions from
a different string, using the expressions that were originally evaluated
when the i-string was constructed. This is needed for the i18n case. I
realize i18n might never use this, but it's a useful thought experiment
in any case.
Eric.
> Here's a slightly reworked version to compare to.
>
> Hope this is helpful,
> Ron
>
>
>
> import sys
> import _string
>
> def interleave(*iters):
> result = []
> for items in zip(*iters):
> for item in items:
> result.append(item)
> return result
>
>
> # i-string
> class i:
> def __init__(self, s):
> self.s = s
> locals = sys._getframe(1).f_locals
> globals = sys._getframe(1).f_globals
> self.literals = []
> self.values = []
> # Evaluate the expressions now, and remember them.
> # This freezes the value at execution time.
> for literal, expr, format_spec, conversion in \
> _string.formatter_parser(self.s):
> self.literals.append(literal)
> if expr:
> value = eval(expr, locals, globals)
> self.values.append(value.__format__(format_spec))
> else:
> self.values.append('')
>
> def __str__(self):
> return ''.join(interleave(self.literals, self.values))
>
>
>
> # f-string
> def f(s):
> return str(i(s))
>
>
> # logging
> def log(istring, echo=True):
> logged = 'log:' + str(istring)
> print(logged)
> return logged
>
>
>
> # test
>
> if __name__ == '__main__':
>
> x = i('Version in caps {sys.version.upper()!r}')
> print(str(x))
>
>
> name = 'Eric'
> dog = 'Fido'
> s = f('My name is {name}, my dog is {dog}')
> print(repr(s))
> assert repr(s) == "'My name is Eric, my dog is Fido'"
> assert type(s) == str
>
>
> import datetime
> def func(value):
> return i('called func with "{value:10}"')
>
> logline = 'as of {now:%Y-%m-%d} the value is {400+1:#06x}'
> now = datetime.datetime(2015, 8, 10, 12, 13, 15)
> logged = log(i(logline), echo=True)
> assert logged == "log:as of 2015-08-10 the value is 0x0191"
>
> now = datetime.datetime(2015, 8, 11, 12, 13, 15)
> logged = log(i(logline), echo=True)
> assert logged == "log:as of 2015-08-11 the value is 0x0191"
>
> logged = log(i('{func(42)}'))
> assert logged == 'log:called func with " 42"'
>
>
> import re
> delimiter = '+'
> trailing_re = re.escape(r'\S+')
> regex = i(r'{delimiter}\d+{delimiter}{trailing_re}')
> print(regex)
> assert str(regex) == r"+\d++\\S\+"
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
More information about the Python-ideas
mailing list