首页 > 解决方案 > UserMixin 继承扰乱了python list.count() 方法

问题描述

我正在使用 list.count() 方法来检查关系是否有元素。

虽然它在测试代码中运行良好,但当计数类继承flask_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,即使我附加了更多整数)。

就好像它在计算列表中的类出现次数,而不是对象。

标签: pythonsqlalchemy

解决方案


首先让我们了解如何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))将使用 指定的操作来比较o1o2使用 ,该操作opid必须是Py_LTPy_LEPy_EQPy_NEPy_GT或中的一个Py_GE,分别对应于<<===!=>>=。如果结果为假,则返回-1错误,否则。01

因此,如果值为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

现在让我们回到你的问题。

虽然它在测试代码中运行良好,但当计数类继承flask_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(即对象的内存地址)与, ,的p1id 进行比较。由于每个新对象都有不同的 id,count 方法将返回。p1p2p31

好的,那么如果我们想在名字相同的情况下将这个人算作一个呢???

让我们举同样的例子。

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

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

但是这仍然返回1,因为 python 仍然与其对象 ID 进行比较。那么我该如何定制呢?您可以覆盖__eq__对象。IE,

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类在类级别上设置id属性,因此所有实例都相同。

如何解决这个问题,

从逻辑的角度来看,每个对象都有唯一的 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

推荐阅读