Я использую метод 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, даже если я добавляю больше целых чисел).
Это как если бы он считал вхождения класса в списке, а не в объекте.
Прежде всего, давайте 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
Эта проблема 'id' является ключевой.
Возвращаясь к контексту sqlalchemy, список содержит объекты с идентификатором "primarykey"... сначала для всех объектов установлено значение "None".
И он будет обновляться только после окончательного исправления session.add() и session.commit().
Благодарю.