From 1c097b7102c6c5a1a03cbb5d36c819eedae60d5a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Walter=20D=C3=B6rwald?= Date: Mon, 22 Apr 2002 11:57:06 +0000 Subject: [PATCH] Backport the following changes: Misc/NEWS 1.387->1.388 Lib/test/string_tests.py 1.10->1.11, 1.12->1.14, Lib/test/test_unicode.py 1.50->1.51, 1.53->1.54, 1.55->1.56 Lib/test/test_string.py 1.15->1.16 Lib/string.py 1.61->1.63 Lib/test/test_userstring.py 1.5->1.6, 1.11, 1.12 Objects/stringobject.c 2.156->2.159 Objects/unicodeobject.c 2.137->2.139 Doc/lib/libstdtypes.tec 1.87->1.88 Add a method zfill to str, unicode and UserString and change Lib/string.py accordingly (see SF patch http://www.python.org/sf/536241) This also adds Guido's fix to test_userstring.py and the subinstance checks in test_string.py and test_unicode.py. --- Doc/lib/libstdtypes.tex | 6 +++ Lib/UserString.py | 1 + Lib/string.py | 16 +++---- Lib/test/string_tests.py | 13 ++++++ Lib/test/test_string.py | 19 ++++++++ Lib/test/test_unicode.py | 15 ++++++- Lib/test/test_userstring.py | 30 ++++++------- Misc/NEWS | 4 ++ Objects/stringobject.c | 87 ++++++++++++++++++++++++++++++------- Objects/unicodeobject.c | 18 +++++--- 10 files changed, 162 insertions(+), 47 deletions(-) diff --git a/Doc/lib/libstdtypes.tex b/Doc/lib/libstdtypes.tex index adf953665b34..1edc8bb99c3a 100644 --- a/Doc/lib/libstdtypes.tex +++ b/Doc/lib/libstdtypes.tex @@ -689,6 +689,12 @@ must be a string of length 256. Return a copy of the string converted to uppercase. \end{methoddesc} +\begin{methoddesc}[string]{zfill}{width} +Return the numeric string left filled with zeros in a string +of length \var{width}. The original string is returned if +\var{width} is less than \code{len(\var{s})}. +\end{methoddesc} + \subsubsection{String Formatting Operations \label{typesseq-strings}} diff --git a/Lib/UserString.py b/Lib/UserString.py index f4f5cab96ced..292e85242d8a 100755 --- a/Lib/UserString.py +++ b/Lib/UserString.py @@ -128,6 +128,7 @@ class UserString: def translate(self, *args): return self.__class__(self.data.translate(*args)) def upper(self): return self.__class__(self.data.upper()) + def zfill(self, width): return self.__class__(self.data.zfill(width)) class MutableString(UserString): """mutable string objects diff --git a/Lib/string.py b/Lib/string.py index a416530e5d9c..cd9909e26617 100644 --- a/Lib/string.py +++ b/Lib/string.py @@ -190,7 +190,10 @@ def rfind(s, *args): _float = float _int = int _long = long -_StringType = type('') +try: + _StringTypes = (str, unicode) +except NameError: + _StringTypes = (str,) # Convert string to float def atof(s): @@ -276,14 +279,9 @@ def zfill(x, width): of the specified width. The string x is never truncated. """ - if type(x) == type(''): s = x - else: s = `x` - n = len(s) - if n >= width: return s - sign = '' - if s[0] in ('-', '+'): - sign, s = s[0], s[1:] - return sign + '0'*(width-n) + s + if not isinstance(x, _StringTypes): + x = repr(x) + return x.zfill(width) # Expand tabs in a string. # Doesn't take non-printing chars into account, but does understand \n. diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py index 3a302c6670ca..180072c2fca4 100644 --- a/Lib/test/string_tests.py +++ b/Lib/test/string_tests.py @@ -227,6 +227,19 @@ def run_method_tests(test): test('endswith', 'ab', 0, 'ab', 0, 1) test('endswith', 'ab', 0, 'ab', 0, 0) + test('zfill', '123', '123', 2) + test('zfill', '123', '123', 3) + test('zfill', '123', '0123', 4) + test('zfill', '+123', '+123', 3) + test('zfill', '+123', '+123', 4) + test('zfill', '+123', '+0123', 5) + test('zfill', '-123', '-123', 3) + test('zfill', '-123', '-123', 4) + test('zfill', '-123', '-0123', 5) + test('zfill', '', '000', 3) + test('zfill', '34', '34', 1) + test('zfill', '34', '0034', 4) + # Encoding/decoding codecs = [('rot13', 'uryyb jbeyq'), ('base64', 'aGVsbG8gd29ybGQ=\n'), diff --git a/Lib/test/test_string.py b/Lib/test/test_string.py index 4c5a60aa2cd8..8f0ea47bd0a1 100644 --- a/Lib/test/test_string.py +++ b/Lib/test/test_string.py @@ -22,6 +22,25 @@ def test(name, input, output, *args): except: value = sys.exc_type f = name + if value == output: + # if the original is returned make sure that + # this doesn't happen with subclasses + if value is input: + class ssub(str): + def __repr__(self): + return 'ssub(%r)' % str.__repr__(self) + input = ssub(input) + try: + f = getattr(input, name) + value = apply(f, args) + except AttributeError: + f = getattr(string, name) + value = apply(f, (input,) + args) + if value is input: + if verbose: + print 'no' + print '*',f, `input`, `output`, `value` + return if value != output: if verbose: print 'no' diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index df5d6159ba0b..4e96752b76c4 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -6,7 +6,7 @@ Written by Marc-Andre Lemburg (mal@lemburg.com). """#" from test_support import verify, verbose, TestFailed -import sys +import sys, string if not sys.platform.startswith('java'): # Test basic sanity of repr() @@ -204,6 +204,19 @@ if 0: test('capwords', u'abc\tdef\nghi', u'Abc Def Ghi') test('capwords', u'abc\t def \nghi', u'Abc Def Ghi') +test('zfill', u'123', u'123', 2) +test('zfill', u'123', u'123', 3) +test('zfill', u'123', u'0123', 4) +test('zfill', u'+123', u'+123', 3) +test('zfill', u'+123', u'+123', 4) +test('zfill', u'+123', u'+0123', 5) +test('zfill', u'-123', u'-123', 3) +test('zfill', u'-123', u'-123', 4) +test('zfill', u'-123', u'-0123', 5) +test('zfill', u'', u'000', 3) +test('zfill', u'34', u'34', 1) +test('zfill', u'34', u'00034', 5) + # Comparisons: print 'Testing Unicode comparisons...', verify(u'abc' == 'abc') diff --git a/Lib/test/test_userstring.py b/Lib/test/test_userstring.py index a8df84d7ba32..86997ce3741e 100755 --- a/Lib/test/test_userstring.py +++ b/Lib/test/test_userstring.py @@ -8,15 +8,15 @@ import string_tests from UserString import UserString if __name__ == "__main__": - verbose = 0 + verbose = '-v' in sys.argv tested_methods = {} -def test(methodname, input, *args): +def test(methodname, input, output, *args): global tested_methods tested_methods[methodname] = 1 if verbose: - print '%s.%s(%s) ' % (input, methodname, args), + print '%r.%s(%s)' % (input, methodname, ", ".join(map(repr, args))), u = UserString(input) objects = [input, u, UserString(u)] res = [""] * 3 @@ -24,22 +24,20 @@ def test(methodname, input, *args): object = objects[i] try: f = getattr(object, methodname) - res[i] = apply(f, args) - except: - res[i] = sys.exc_type - if res[0] != res[1]: - if verbose: - print 'no' - print `input`, f, `res[0]`, "<>", `res[1]` - else: + except AttributeError: + f = None + res[i] = AttributeError + else: + try: + res[i] = apply(f, args) + except: + res[i] = sys.exc_type + if res[0] == res[1] == res[2] == output: if verbose: print 'yes' - if res[1] != res[2]: - if verbose: - print 'no' - print `input`, f, `res[1]`, "<>", `res[2]` else: if verbose: - print 'yes' + print 'no' + print (methodname, input, output, args, res[0], res[1], res[2]) string_tests.run_method_tests(test) diff --git a/Misc/NEWS b/Misc/NEWS index 0507950048c0..48c9b321a6b3 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -8,6 +8,10 @@ Core and builtins great many cyclic structures involving frames. Reported on SourceForge as bug 543148. +- A method zfill() was added to str and unicode, that fills a numeric + string to the left with zeros. For example, + "+123".zfill(6) -> "+00123". + - Complex numbers supported divmod() and the // and % operators, but these make no sense. Since this was documented, they're being deprecated now. diff --git a/Objects/stringobject.c b/Objects/stringobject.c index 95bc79abc12e..feb5947c3a59 100644 --- a/Objects/stringobject.c +++ b/Objects/stringobject.c @@ -1,4 +1,3 @@ - /* String object implementation */ #include "Python.h" @@ -605,7 +604,8 @@ string_print(PyStringObject *op, FILE *fp, int flags) /* figure out which quote to use; single is preferred */ quote = '\''; - if (strchr(op->ob_sval, '\'') && !strchr(op->ob_sval, '"')) + if (strchr(op->ob_sval, '\'') && + !strchr(op->ob_sval, '"')) quote = '"'; fputc(quote, fp); @@ -649,7 +649,8 @@ string_repr(register PyStringObject *op) /* figure out which quote to use; single is preferred */ quote = '\''; - if (strchr(op->ob_sval, '\'') && !strchr(op->ob_sval, '"')) + if (strchr(op->ob_sval, '\'') && + !strchr(op->ob_sval, '"')) quote = '"'; p = PyString_AS_STRING(v); @@ -2414,6 +2415,52 @@ string_center(PyStringObject *self, PyObject *args) return pad(self, left, marg - left, ' '); } +static char zfill__doc__[] = +"S.zfill(width) -> string\n" +"\n" +"Pad a numeric string S with zeros on the left, to fill a field\n" +"of the specified width. The string S is never truncated."; + +static PyObject * +string_zfill(PyStringObject *self, PyObject *args) +{ + int fill; + PyObject *s; + char *p; + + int width; + if (!PyArg_ParseTuple(args, "i:zfill", &width)) + return NULL; + + if (PyString_GET_SIZE(self) >= width) { + if (PyString_CheckExact(self)) { + Py_INCREF(self); + return (PyObject*) self; + } + else + return PyString_FromStringAndSize( + PyString_AS_STRING(self), + PyString_GET_SIZE(self) + ); + } + + fill = width - PyString_GET_SIZE(self); + + s = pad(self, fill, 0, '0'); + + if (s == NULL) + return NULL; + + p = PyString_AS_STRING(s); + if (p[fill] == '+' || p[fill] == '-') { + /* move sign to beginning of string */ + p[0] = p[fill]; + p[fill] = '0'; + } + + return (PyObject*) s; +} + static char isspace__doc__[] = "S.isspace() -> int\n" "\n" @@ -2743,9 +2790,11 @@ string_methods[] = { {"istitle", (PyCFunction)string_istitle, METH_NOARGS, istitle__doc__}, {"isalpha", (PyCFunction)string_isalpha, METH_NOARGS, isalpha__doc__}, {"isalnum", (PyCFunction)string_isalnum, METH_NOARGS, isalnum__doc__}, - {"capitalize", (PyCFunction)string_capitalize, METH_NOARGS, capitalize__doc__}, + {"capitalize", (PyCFunction)string_capitalize, METH_NOARGS, + capitalize__doc__}, {"count", (PyCFunction)string_count, METH_VARARGS, count__doc__}, - {"endswith", (PyCFunction)string_endswith, METH_VARARGS, endswith__doc__}, + {"endswith", (PyCFunction)string_endswith, METH_VARARGS, + endswith__doc__}, {"find", (PyCFunction)string_find, METH_VARARGS, find__doc__}, {"index", (PyCFunction)string_index, METH_VARARGS, index__doc__}, {"lstrip", (PyCFunction)string_lstrip, METH_VARARGS, lstrip__doc__}, @@ -2753,21 +2802,24 @@ string_methods[] = { {"rfind", (PyCFunction)string_rfind, METH_VARARGS, rfind__doc__}, {"rindex", (PyCFunction)string_rindex, METH_VARARGS, rindex__doc__}, {"rstrip", (PyCFunction)string_rstrip, METH_VARARGS, rstrip__doc__}, - {"startswith", (PyCFunction)string_startswith, METH_VARARGS, startswith__doc__}, + {"startswith", (PyCFunction)string_startswith, METH_VARARGS, + startswith__doc__}, {"strip", (PyCFunction)string_strip, METH_VARARGS, strip__doc__}, - {"swapcase", (PyCFunction)string_swapcase, METH_NOARGS, swapcase__doc__}, - {"translate", (PyCFunction)string_translate, METH_VARARGS, translate__doc__}, + {"swapcase", (PyCFunction)string_swapcase, METH_NOARGS, + swapcase__doc__}, + {"translate", (PyCFunction)string_translate, METH_VARARGS, + translate__doc__}, {"title", (PyCFunction)string_title, METH_NOARGS, title__doc__}, {"ljust", (PyCFunction)string_ljust, METH_VARARGS, ljust__doc__}, {"rjust", (PyCFunction)string_rjust, METH_VARARGS, rjust__doc__}, {"center", (PyCFunction)string_center, METH_VARARGS, center__doc__}, + {"zfill", (PyCFunction)string_zfill, METH_VARARGS, zfill__doc__}, {"encode", (PyCFunction)string_encode, METH_VARARGS, encode__doc__}, {"decode", (PyCFunction)string_decode, METH_VARARGS, decode__doc__}, - {"expandtabs", (PyCFunction)string_expandtabs, METH_VARARGS, expandtabs__doc__}, - {"splitlines", (PyCFunction)string_splitlines, METH_VARARGS, splitlines__doc__}, -#if 0 - {"zfill", (PyCFunction)string_zfill, METH_VARARGS, zfill__doc__}, -#endif + {"expandtabs", (PyCFunction)string_expandtabs, METH_VARARGS, + expandtabs__doc__}, + {"splitlines", (PyCFunction)string_splitlines, METH_VARARGS, + splitlines__doc__}, {NULL, NULL} /* sentinel */ }; @@ -3262,7 +3314,8 @@ PyString_Format(PyObject *format, PyObject *args) char *pbuf; int sign; int len; - char formatbuf[FORMATBUFLEN]; /* For format{float,int,char}() */ + char formatbuf[FORMATBUFLEN]; + /* For format{float,int,char}() */ #ifdef Py_USING_UNICODE char *fmt_start = fmt; int argidx_start = argidx; @@ -3460,7 +3513,8 @@ PyString_Format(PyObject *format, PyObject *args) } else { pbuf = formatbuf; - len = formatint(pbuf, sizeof(formatbuf), + len = formatint(pbuf, + sizeof(formatbuf), flags, prec, c, v); if (len < 0) goto error; @@ -3476,7 +3530,8 @@ PyString_Format(PyObject *format, PyObject *args) case 'g': case 'G': pbuf = formatbuf; - len = formatfloat(pbuf, sizeof(formatbuf), flags, prec, c, v); + len = formatfloat(pbuf, sizeof(formatbuf), + flags, prec, c, v); if (len < 0) goto error; sign = 1; diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 5a8777b39643..c797be01b87b 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -4810,7 +4810,6 @@ unicode_upper(PyUnicodeObject *self) return fixup(self, fixupper); } -#if 0 static char zfill__doc__[] = "S.zfill(width) -> unicode\n\ \n\ @@ -4828,14 +4827,24 @@ unicode_zfill(PyUnicodeObject *self, PyObject *args) return NULL; if (self->length >= width) { - Py_INCREF(self); - return (PyObject*) self; + if (PyUnicode_CheckExact(self)) { + Py_INCREF(self); + return (PyObject*) self; + } + else + return PyUnicode_FromUnicode( + PyUnicode_AS_UNICODE(self), + PyUnicode_GET_SIZE(self) + ); } fill = width - self->length; u = pad(self, fill, 0, '0'); + if (u == NULL) + return NULL; + if (u->str[fill] == '+' || u->str[fill] == '-') { /* move sign to beginning of string */ u->str[0] = u->str[fill]; @@ -4844,7 +4853,6 @@ unicode_zfill(PyUnicodeObject *self, PyObject *args) return (PyObject*) u; } -#endif #if 0 static PyObject* @@ -4956,8 +4964,8 @@ static PyMethodDef unicode_methods[] = { {"isnumeric", (PyCFunction) unicode_isnumeric, METH_NOARGS, isnumeric__doc__}, {"isalpha", (PyCFunction) unicode_isalpha, METH_NOARGS, isalpha__doc__}, {"isalnum", (PyCFunction) unicode_isalnum, METH_NOARGS, isalnum__doc__}, -#if 0 {"zfill", (PyCFunction) unicode_zfill, METH_VARARGS, zfill__doc__}, +#if 0 {"capwords", (PyCFunction) unicode_capwords, METH_NOARGS, capwords__doc__}, #endif -- 2.47.3