Почему строки в Python 2.7 не имеют атрибута "__iter__", а строки в Python 3.7 имеют атрибут "__iter__" - PullRequest
2 голосов
/ 10 октября 2019

И в python 2.7, и в python 3.7 я могу сделать следующее:

string = "hello world"
for letter in string:
    print(letter)

Но если я проверю наличие повторяемого атрибута:

python 2.7

hasattr(string, "__iter__")
>> False

python 3.7

hasattr(string, "__iter__")
>> True

Это то, что я не нашел документированного при выполнении миграции на 2to3, и это вызвало небольшую головную боль, потому что в моем коде есть проверки hasattr(entity, "__iter__"). В python 2.7 это работает, чтобы отличить строку от коллекции, но не в python 3.7

На самом деле я спрашиваю, каково было проектное решение, почему это было реализовано так. Надеюсь, это не слишком умозрительно.

1 Ответ

3 голосов
/ 10 октября 2019

Класс str_iterator для str (прогон type(iter("str"))) был реализован в CPython3. Объект, который возвращается при вызове "str".__iter__() или iter("str"). В CPython2 собственный класс итератора для str не был реализован. Когда вы вызываете iter("str"), вы получаете экземпляр базового класса итератора.

См. 1 часть кода. f = t->tp_iter; - вернуть пользовательский итератор, если он существует. В противном случае PySeqIter_New(o) - вернуть экземпляр структуры seqiterobject (см. 2 фрагмента кода). Как видно из 3 фрагментов кода, этот базовый итератор вызывает итерацию PySequence_GetItem(seq, it->it_index);.

Если вы реализуете свой класс, поддерживающий итерацию, то вам нужно определить либо метод __iter__, либо __getitem__. Если вы выберете первую опцию, то в возвращаемом объекте должны быть реализованы методы __iter__ и __next__ (CPython3) или next (CPython2).

Плохая идея проверить на hasattr(entity, "__iter__").

  • Если вам нужно проверить, является ли объект повторяемым, запустите isinstance(entity, Iterable).
  • Если вам нужно исключить только str, запустите isinstance(entity, Iterable) and not isinstance(entity, str) (CPython3).

1

PyObject *
PyObject_GetIter(PyObject *o)
{
    PyTypeObject *t = o->ob_type;
    getiterfunc f;

    f = t->tp_iter;
    if (f == NULL) {
         if (PySequence_Check(o)) return PySeqIter_New(o);
         return type_error("'%.200s' object is not iterable", o);
    }
    else {
        ...
    }
}

2

typedef struct {
    PyObject_HEAD
    Py_ssize_t it_index;
    PyObject *it_seq; /* Set to NULL when iterator is exhausted */
} seqiterobject;

PyObject *
PySeqIter_New(PyObject *seq)
{
    seqiterobject *it;

    if (!PySequence_Check(seq)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    it = PyObject_GC_New(seqiterobject, &PySeqIter_Type);
    if (it == NULL)
        return NULL;
    it->it_index = 0;
    Py_INCREF(seq);
    it->it_seq = seq;
    _PyObject_GC_TRACK(it);
    return (PyObject *)it;
}

3

static PyObject *
iter_iternext(PyObject *iterator)
{
    seqiterobject *it;
    PyObject *seq;
    PyObject *result;

    assert(PySeqIter_Check(iterator));
    it = (seqiterobject *)iterator;
    seq = it->it_seq;
    if (seq == NULL)
        return NULL;
    if (it->it_index == PY_SSIZE_T_MAX) {
        PyErr_SetString(PyExc_OverflowError, "iter index too large");
        return NULL;
    }

    result = PySequence_GetItem(seq, it->it_index);
    if (result != NULL) {
        it->it_index++;
        return result;
    }
    if (PyErr_ExceptionMatches(PyExc_IndexError) ||
        PyErr_ExceptionMatches(PyExc_StopIteration))
    {
        PyErr_Clear();
        it->it_seq = NULL;
        Py_DECREF(seq);
    }
    return NULL;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...