Наследование UserMixin нарушает метод python list.count () - PullRequest
0 голосов
/ 11 января 2019

Я использую метод list.count (), чтобы проверить, есть ли у отношения элемент.

Хотя он работает довольно хорошо в тестовом коде, он больше не работает, когда подсчитанный класс наследует класс UserMixin flask_login.

Почему и как это исправить?

class Element(UserMixin):
    id=1
    name="default"
    def __init__(self, name):
        name=name

elementsList=[]


elt1=Element(name="1")
elt2=Element(name="2")
elt3=Element(name="3")

elementsList.append(elt1)
elementsList.append(elt2)


print("Counting Element2 should return 1: ", elementsList.count(elt2)) # returns 2
print("Counting Element3 should return 0: ", elementsList.count(elt3)) # returns 2

Я должен получить количество элементов в списке (1 или 0).

Вместо этого я получаю всю длину списка (2, даже если я добавляю больше целых чисел).

Это как если бы он подсчитывал вхождения класса в списке, а не объект.

Ответы [ 2 ]

0 голосов
/ 11 января 2019

Эта проблема 'id' является ключевой.

Возвращаясь к контексту sqlalchemy, список содержит объекты с идентификатором в качестве primarykey ... сначала для всех объектов установлено значение "None".

И оно будет обновлено только после окончательного исправления session.add () и session.commit ().

спасибо.

0 голосов
/ 11 января 2019

Прежде всего давайте разберемся, как работает list.count. Из исходного кода cpython list.count имеет следующее определение.

static PyObject *
list_count(PyListObject *self, PyObject *value)
{
    Py_ssize_t count = 0;
    Py_ssize_t i;

    for (i = 0; i < Py_SIZE(self); i++) {
        int cmp = PyObject_RichCompareBool(self->ob_item[i], value, Py_EQ);
        if (cmp > 0)
            count++;
        else if (cmp < 0)
            return NULL;
    }
    return PyLong_FromSsize_t(count);
}

Таким образом, когда вы выполняете some_list.count(some_element), Python будет перебирать каждый объект в списке и выполнять расширенное сравнение (то есть PyObject_RichCompareBool).

Из документации C-API расширенное сравнение (т. Е. PyObject_RichCompareBool(PyObject *o1, PyObject *o2, int opid)) будет Сравните значения o1 и o2, используя операцию, указанную в opid, которая должна быть одной из Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT или Py_GE, соответствует <, <=, ==, !=, > или >= соответственно. Возвращает -1 при ошибке, 0, если результат ложен, 1 в противном случае.

Таким образом, если значение равно 1 (то есть true), счетчик будет увеличиваться. После итерации счетчик будет возвращен вызывающей стороне.

list_count в CPython примерно эквивалентно следующему в слое python,

def list_count(list_, item_to_count):
   counter = 0
   for iterm in list_:
      if item == item_to_count:
          counter += 1
   return counter

Теперь вернемся к вашему вопросу.

Хотя в тестовом коде он работает довольно хорошо, он больше не работает, когда подсчитанный класс наследует класс UserMixin flask_login.

Давайте возьмем образец класса (без наследования от UserMixin)

class Person
   def __init__(self, name):
       self.name = name

p1 = Person("Person1")
p2 = Person("Person2")
p3 = Person("Person3")

print([p1, p2, p3].count(p1))

Это напечатает 1, как мы и ожидали. Но как Python выполняет сравнение здесь ??? По умолчанию python сравнивает id (то есть адрес памяти объекта) p1 с идентификаторами p1, p2, p3. Поскольку каждый новый объект имеет разные идентификаторы, метод count вернет 1.

Хорошо, а что если мы хотим считать человека одним, если там имена равны ???

Давайте возьмем тот же пример.

p1 = Person("Person1")
p2 = Person("Person1")

print([p1, p2].count(p1)) # I want this to be return 2

Но это все еще возвращает 1 как python, по-прежнему сравниваемый с его идентификаторами объектов. Так как я могу настроить это? Вы можете переопределить __eq__ объекта. т.е.

class Person(object):
   def __init__(self, name):
       self.name = name

   def __eq__(self, other):
       if isinstance(other, self.__class__):
           return self.name == other.name
       return NotImplemented

p1 = Person("Person1")
p2 = Person("Person1")

print([p1, p2].count(p1))

Ух ты, теперь он возвращает 2, как и ожидалось.

Теперь давайте рассмотрим класс, который наследуется от UserMixin.

class Element(UserMixin):
    id=1
    def __init__(self, name):
        self.name=name

elementsList=[]
elt1=Element(name="1")
elt2=Element(name="2")
elt3=Element(name="3")
elementsList.append(elt1)
elementsList.append(elt2)
print(elementsList.count(elt2)) 

Будет напечатано 2. Зачем?. Если бы сравнение было выполнено на основе ids, это было бы 1. Так что где-то будет реализовано __eq__. Поэтому, если вы посмотрите на реализацию класса UserMixin, он реализует метод __eq__.

def __eq__(self, other):
    '''
    Checks the equality of two `UserMixin` objects using `get_id`.
    '''
    if isinstance(other, UserMixin):
        return self.get_id() == other.get_id()
    return NotImplemented

def get_id(self):
    try:
        return text_type(self.id)
    except AttributeError:
        raise NotImplementedError('No `id` attribute - override `get_id`')

Как видите, сравнение выполняется по атрибуту id. В этом случае Element class устанавливает атрибут id на уровне класса, следовательно, он будет одинаковым для всех экземпляров.

Как это исправить,

С логической точки зрения каждый объект будет иметь уникальные идентификаторы. Следовательно, id должен быть атрибутом уровня экземпляра. См. Один пример из самой кодовой базы flask-login.

class User(UserMixin):
    def __init__(self, name, id, active=True):
        self.id = id
        self.name = name
        self.active = active

    def get_id(self):
        return self.id

    @property
    def is_active(self):
        return self.active
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...