Skip to content

Commit 8c66326

Browse files
committed
Implementation of PEP 3101, Advanced String Formatting.
Known issues: The string.Formatter class, as discussed in the PEP, is incomplete. Error handling needs to conform to the PEP. Need to fix this warning that I introduced in Python/formatter_unicode.c: Objects/stringlib/unicodedefs.h:26: warning: `STRINGLIB_CMP' defined but not used Need to make sure sign formatting is correct, more tests needed. Need to remove '()' sign formatting, left over from an earlier version of the PEP.
1 parent e4dc324 commit 8c66326

22 files changed

+2669
-18
lines changed

Include/formatter_unicode.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
PyObject *
2+
unicode_unicode__format__(PyObject *self, PyObject *args);
3+
4+
PyObject *
5+
unicode_long__format__(PyObject *self, PyObject *args);
6+
7+
PyObject *
8+
unicode_float__format__(PyObject *self, PyObject *args);
9+

Include/unicodeobject.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,11 @@ PyAPI_FUNC(Py_UNICODE*) Py_UNICODE_strchr(
14371437
const Py_UNICODE *s, Py_UNICODE c
14381438
);
14391439

1440+
PyObject *
1441+
_unicodeformatter_iterator(PyObject *str);
1442+
PyObject *
1443+
_unicodeformatter_lookup(PyObject *field_name, PyObject *args,
1444+
PyObject *kwargs);
14401445

14411446
#ifdef __cplusplus
14421447
}

Lib/string.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,42 @@ def convert(mo):
189189
raise ValueError('Unrecognized named group in pattern',
190190
self.pattern)
191191
return self.pattern.sub(convert, self.template)
192+
193+
194+
195+
########################################################################
196+
# the Formatter class
197+
# see PEP 3101 for details and purpose of this class
198+
199+
# The hard parts are reused from the C implementation. They're
200+
# exposed here via the sys module. sys was chosen because it's always
201+
# available and doesn't have to be dynamically loaded.
202+
203+
# The parser is implemented in sys._formatter_parser.
204+
# The "object lookup" is implemented in sys._formatter_lookup
205+
206+
from sys import _formatter_parser, _formatter_lookup
207+
208+
class Formatter:
209+
def format(self, format_string, *args, **kwargs):
210+
return self.vformat(format_string, args, kwargs)
211+
212+
def vformat(self, format_string, args, kwargs):
213+
result = []
214+
for (is_markup, literal, field_name, format_spec, conversion) in \
215+
_formatter_parser(format_string):
216+
if is_markup:
217+
# find the object
218+
index, name, obj = _formatter_lookup(field_name, args, kwargs)
219+
else:
220+
result.append(literal)
221+
return ''.join(result)
222+
223+
def get_value(self, key, args, kwargs):
224+
pass
225+
226+
def check_unused_args(self, used_args, args, kwargs):
227+
pass
228+
229+
def format_field(self, value, format_spec):
230+
pass

Lib/test/test_builtin.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,32 @@ def __float__(self):
517517
self.assertAlmostEqual(float(Foo3(21)), 42.)
518518
self.assertRaises(TypeError, float, Foo4(42))
519519

520+
def test_format(self):
521+
class A:
522+
def __init__(self, x):
523+
self.x = x
524+
def __format__(self, format_spec):
525+
return str(self.x) + format_spec
526+
527+
# class that returns a bad type from __format__
528+
class H:
529+
def __format__(self, format_spec):
530+
return 1.0
531+
532+
self.assertEqual(format(3, ''), '3')
533+
self.assertEqual(format(A(3), 'spec'), '3spec')
534+
535+
# for builtin types, format(x, "") == str(x)
536+
self.assertEqual(format(17**13, ""), str(17**13))
537+
self.assertEqual(format(1.0, ""), str(1.0))
538+
self.assertEqual(format(3.1415e104, ""), str(3.1415e104))
539+
self.assertEqual(format(-3.1415e104, ""), str(-3.1415e104))
540+
self.assertEqual(format(3.1415e-104, ""), str(3.1415e-104))
541+
self.assertEqual(format(-3.1415e-104, ""), str(-3.1415e-104))
542+
self.assertEqual(format(object, ""), str(object))
543+
544+
#self.assertRaises(TypeError, format, H(), "")
545+
520546
def test_getattr(self):
521547
import sys
522548
self.assert_(getattr(sys, 'stdout') is sys.stdout)

Lib/test/test_descrtut.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ def merge(self, other):
173173
'__delslice__',
174174
'__doc__',
175175
'__eq__',
176+
'__format__',
176177
'__ge__',
177178
'__getattribute__',
178179
'__getitem__',

Lib/test/test_float.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,44 @@ def neg_neg():
114114
self.assertEquals(pos_pos(), neg_pos())
115115
self.assertEquals(pos_neg(), neg_neg())
116116

117+
class FormatTestCase(unittest.TestCase):
118+
def testFormat(self):
119+
# these should be rewritten to use both format(x, spec) and
120+
# x.__format__(spec)
121+
122+
self.assertEqual(format(0.0, 'f'), '0.000000')
123+
124+
# the default is 'g', except for empty format spec
125+
self.assertEqual(format(0.0, ''), '0.0')
126+
self.assertEqual(format(0.01, ''), '0.01')
127+
self.assertEqual(format(0.01, 'g'), '0.01')
128+
129+
self.assertEqual(format(0, 'f'), '0.000000')
130+
131+
self.assertEqual(format(1.0, 'f'), '1.000000')
132+
self.assertEqual(format(1, 'f'), '1.000000')
133+
134+
self.assertEqual(format(-1.0, 'f'), '-1.000000')
135+
self.assertEqual(format(-1, 'f'), '-1.000000')
136+
137+
self.assertEqual(format( 1.0, ' f'), ' 1.000000')
138+
self.assertEqual(format(-1.0, ' f'), '-1.000000')
139+
self.assertEqual(format( 1.0, '+f'), '+1.000000')
140+
self.assertEqual(format(-1.0, '+f'), '-1.000000')
141+
142+
# % formatting
143+
self.assertEqual(format(-1.0, '%'), '-100.000000%')
144+
145+
# conversion to string should fail
146+
self.assertRaises(ValueError, format, 3.0, "s")
147+
117148

118149
def test_main():
119150
test_support.run_unittest(
120151
FormatFunctionsTestCase,
121152
UnknownFormatTestCase,
122-
IEEEFormatTestCase)
153+
IEEEFormatTestCase,
154+
FormatTestCase)
123155

124156
if __name__ == '__main__':
125157
test_main()

Lib/test/test_long.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,50 @@ def __cmp__(self, other):
493493
eq(x > y, Rcmp > 0, Frm("%r > %r %d", x, y, Rcmp))
494494
eq(x >= y, Rcmp >= 0, Frm("%r >= %r %d", x, y, Rcmp))
495495

496+
def test_format(self):
497+
self.assertEqual(format(123456789, 'd'), '123456789')
498+
self.assertEqual(format(123456789, 'd'), '123456789')
499+
500+
# hex
501+
self.assertEqual(format(3, "x"), "3")
502+
self.assertEqual(format(3, "X"), "3")
503+
self.assertEqual(format(1234, "x"), "4d2")
504+
self.assertEqual(format(-1234, "x"), "-4d2")
505+
self.assertEqual(format(1234, "8x"), " 4d2")
506+
# XXX fix self.assertEqual(format(-1234, "8x"), " -4d2")
507+
self.assertEqual(format(1234, "x"), "4d2")
508+
self.assertEqual(format(-1234, "x"), "-4d2")
509+
self.assertEqual(format(-3, "x"), "-3")
510+
self.assertEqual(format(-3, "X"), "-3")
511+
self.assertEqual(format(int('be', 16), "x"), "be")
512+
self.assertEqual(format(int('be', 16), "X"), "BE")
513+
self.assertEqual(format(-int('be', 16), "x"), "-be")
514+
self.assertEqual(format(-int('be', 16), "X"), "-BE")
515+
516+
# octal
517+
self.assertEqual(format(3, "b"), "11")
518+
self.assertEqual(format(-3, "b"), "-11")
519+
self.assertEqual(format(1234, "b"), "10011010010")
520+
self.assertEqual(format(-1234, "b"), "-10011010010")
521+
self.assertEqual(format(1234, "-b"), "10011010010")
522+
self.assertEqual(format(-1234, "-b"), "-10011010010")
523+
self.assertEqual(format(1234, " b"), " 10011010010")
524+
self.assertEqual(format(-1234, " b"), "-10011010010")
525+
self.assertEqual(format(1234, "+b"), "+10011010010")
526+
self.assertEqual(format(-1234, "+b"), "-10011010010")
527+
528+
# conversion to float
529+
self.assertEqual(format(0, 'f'), '0.000000')
530+
531+
# make sure these are errors
532+
self.assertRaises(ValueError, format, 3, "1.3") # precision disallowed
533+
return
534+
self.assertRaises(ValueError, format, 3, "+c") # sign not allowed
535+
# with 'c'
536+
self.assertRaises(ValueError, format, 3, "R") # bogus format type
537+
# conversion to string should fail
538+
self.assertRaises(ValueError, format, 3, "s")
539+
496540
def test_main():
497541
test_support.run_unittest(LongTest)
498542

Lib/test/test_string.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ def test_attrs(self):
1515
string.punctuation
1616
string.printable
1717

18+
def test_formatter(self):
19+
fmt = string.Formatter()
20+
self.assertEqual(fmt.format("foo"), "foo")
21+
22+
# Formatter not working you for lookups
23+
#self.assertEqual(fmt.format("foo{0}", "bar"), "foobar")
24+
25+
1826
def test_maketrans(self):
1927
transtable = '\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`xyzdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377'
2028

0 commit comments

Comments
 (0)