Какой самый питонический способ дополнить числовую строку нулями слева, то есть, чтобы числовая строка имела определенную длину?
str.zfill
специально предназначено для этого:
>>> '1'.zfill(4)
'0001'
Обратите внимание, что он специально предназначен для обработки числовых строк в соответствии с запросом и перемещает +
или -
в начало строки:
>>> '+1'.zfill(4)
'+001'
>>> '-1'.zfill(4)
'-001'
Вот справка по str.zfill
:
>>> help(str.zfill)
Help on method_descriptor:
zfill(...)
S.zfill(width) -> str
Pad a numeric string S with zeros on the left, to fill a field
of the specified width. The string S is never truncated.
Производительность
Это также самый эффективный из альтернативных методов:
>>> min(timeit.repeat(lambda: '1'.zfill(4)))
0.18824880896136165
>>> min(timeit.repeat(lambda: '1'.rjust(4, '0')))
0.2104538488201797
>>> min(timeit.repeat(lambda: f'{1:04}'))
0.32585487607866526
>>> min(timeit.repeat(lambda: '{:04}'.format(1)))
0.34988890308886766
Чтобы лучше сравнить яблоки с яблоками для метода %
(обратите внимание, что он на самом деле медленнее), который в противном случае будет предварительно вычислять:
>>> min(timeit.repeat(lambda: '1'.zfill(0 or 4)))
0.19728074967861176
>>> min(timeit.repeat(lambda: '%04d' % (0 or 1)))
0.2347015216946602
Осуществление
Немного покопавшись, я нашел реализацию метода zfill
в Objects/stringlib/transmogrify.h
:
static PyObject *
stringlib_zfill(PyObject *self, PyObject *args)
{
Py_ssize_t fill;
PyObject *s;
char *p;
Py_ssize_t width;
if (!PyArg_ParseTuple(args, "n:zfill", &width))
return NULL;
if (STRINGLIB_LEN(self) >= width) {
return return_self(self);
}
fill = width - STRINGLIB_LEN(self);
s = pad(self, fill, 0, '0');
if (s == NULL)
return NULL;
p = STRINGLIB_STR(s);
if (p[fill] == '+' || p[fill] == '-') {
/* move sign to beginning of string */
p[0] = p[fill];
p[fill] = '0';
}
return s;
}
Давайте пройдемся по этому C-коду.
Сначала он анализирует аргумент позиционно, то есть он не допускает аргументы ключевого слова:
>>> '1'.zfill(width=4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: zfill() takes no keyword arguments
Затем проверяется, имеет ли он ту же длину или больше, и в этом случае возвращает строку.
>>> '1'.zfill(0)
'1'
zfill
вызывает pad
(эта функция pad
также вызывается ljust
, rjust
и center
). Это в основном копирует содержимое в новую строку и заполняет отступы.
static inline PyObject *
pad(PyObject *self, Py_ssize_t left, Py_ssize_t right, char fill)
{
PyObject *u;
if (left < 0)
left = 0;
if (right < 0)
right = 0;
if (left == 0 && right == 0) {
return return_self(self);
}
u = STRINGLIB_NEW(NULL, left + STRINGLIB_LEN(self) + right);
if (u) {
if (left)
memset(STRINGLIB_STR(u), fill, left);
memcpy(STRINGLIB_STR(u) + left,
STRINGLIB_STR(self),
STRINGLIB_LEN(self));
if (right)
memset(STRINGLIB_STR(u) + left + STRINGLIB_LEN(self),
fill, right);
}
return u;
}
После вызова pad
, zfill
перемещает любой изначально предшествующий +
или -
в начало строки.
Обратите внимание, что для того, чтобы исходная строка была фактически числовой, не требуется:
>>> '+foo'.zfill(10)
'+000000foo'
>>> '-foo'.zfill(10)
'-000000foo'