Наследование UserMixin нарушает метод python list.count ()

1

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

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

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

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, даже если я добавляю больше целых чисел).

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

Теги:
sqlalchemy

2 ответа

0
Лучший ответ

Прежде всего, давайте list.count как работает list.count. Из исходного кода 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)) будет 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

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

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

Давайте возьмем пример класса (без наследования от 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 класса 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 устанавливает атрибут 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
0

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

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

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

Благодарю.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню