问题
I have a method that receives a format string and a set of values provided by a user, and uses these to write an output to a screen.
def makestring(fmt, vals):
s = fmt.format(*vals)
return s
fmt_expecting_three = 'a={:0.2f}, b={:0.4f}, c={:0.1f}'
threevalues = [(x+1)/7. for x in range(3)]
makestring(fmt_expecting_three, threevalues)
produces
'a=0.14, b=0.2857, c=0.4'
I would like to perform a test to discover the number of values matches what the format is "expecting".
I show an ugly test below, that can give incorrect results if you don't set maxcheck high enough. Is there a more natural, less ugly way to find out how many values are expected?
def checkit(fmt, maxcheck=None):
if maxcheck == None:
maxcheck = 10
for i in range(maxcheck-1, 0, -1):
try:
fmt.format(*range(i))
except:
return i+1
fmt_expecting_three = 'a={:0.2f}, b={:0.4f}, c={:0.1f}'
checkit(fmt_expecting_three)
returns
3
回答1:
I'd do this with string.Formatter. This ensures that the string is actually a valid format string, whilst giving you all the information about the format. Take:
>>> import string
>>> f = string.Formatter()
>>> l = list(f.parse('Hello, {noun!s: ^4} world{}!'))
>>> l
[('Hello, ', 'noun', ' ^4', 's'), (' world', '', '', None), ('!', None, None, None)]
From this you can count the amount by checking if the second item is None or not.
>>> sum(1 for _, field_name, _, _ in l if field_name is not None)
2
And so you can use:
def count_formats(format_string):
f = string.Formatter()
formats = f.parse(format_string)
return sum(1 for _, field_name, _, _ in formats if field_name is not None)
This however doesn't work with nested formats. And so we need to check what can be nested:
>>> list(f.parse('{}'))
[('', '', '', None)]
>>> list(f.parse('{:{}}'))
[('', '', '{}', None)]
>>> list(f.parse('{{}:{}}'))
ValueError: Single '}' encountered in format string
>>> list(f.parse('{!{}:{}}'))
ValueError: Single '}' encountered in format string
And so we only need to check format spec to see if there are any nested formats. And so you can change count_formats to be nested if you'd like:
def count_formats(format_string):
def nested(s):
for hit in f.parse(s):
yield hit
if hit[2]:
for nested_hit in nested(hit[2]):
yield nested_hit
f = string.Formatter()
formats = nested(format_string)
return sum(1 for _, field_name, _, _ in formats if field_name is not None)
回答2:
Though learning regex is painful, it pays off!
Modified from http://www.rexegg.com/regex-cookbook.html
things = ['pi={:0.2f}, e={:0.4f}', 'pi={:0.2f}, e={q', '{{}',
'{}wow', 'wow', '{}'] # includes some pathologicals
import re
for thing in things:
print len(re.findall('{([^{)]*)}', thing)), thing
returns
2 pi={:0.2f}, e={:0.4f}
1 pi={:0.2f}, e={q
1 {{}
1 {}wow
0 wow
1 {}
yay!
回答3:
For something like this, I tend to look at the way the Python interpreter solves the problem, rather than reinventing the wheel.
I found a C function in python 3 called countformat(). (This also looks about the same in Python 2.7.)
Next step would be to see if this function is exposed, or is strictly C. I did a code search. It looks like the answer is no, so I'd probably just copy whatever they did but do it in Python instead. It might be more efficient to write a regular expression, but that C function at least gives you an idea of how to define your regex.
The general algorithm is as follows:
- Set level to 0 and count to 0.
- Iterate through the format string. For each character:
- If you encounter an opening format marker
(,[,{:- If level is 0, increment count.
- Increment level.
- Else if you encounter a closing format marker
),],}:- If level is 0, decrement count.
- Decrement level.
- Else if you find some other marker
#,&,:,,\t:- Do nothing.
- Else (some other character):
- If level is 0, increment count.
- Return count.
来源:https://stackoverflow.com/questions/51609953/evaluate-python-format-string-for-number-of-values-expected