Python Astroid (Pylint) class 通过工厂时如何模拟继承?

Python Astroid (Pylint) How to mimic inheritance when class passes through factory?

在我工作的地方,我们广泛使用 SQLAlchemy。随着时间的推移,我们为满足我们需求的模型开发了一个基础 class。但是,当需要对我们的代码进行 lint 时,我们总是被我们知道可以忽略的警告所淹没。但到目前为止,我们只能通过 generated-members 指令在全球范围内做到这一点,这往往会隐藏问题。 所以我开始想:“我怎么能把它教给 pylint 呢?”

情况如下:

from sqlalchemy.ext.declarative import declarative_base

class CustomBaseModel(object):
    def feature(self):
        pass

Model = declarative_base(cls=CustomBaseModel)

class Thing(Model):
    id = Column(Integer, primary_key=True)
    label = Column(String(64))

t = Thing()
t.feature()  # Pylint says Thing has not `feature()` method.

所以我想做的是告诉 pylint 模型实际上或多或少是 CustomBaseModel。

因此看起来我应该在调用 declarative_base() 的 return 值上使用 inference_tip。但我不确定如何进行。看起来 API 随着时间的推移而改变,我不会去任何地方。

我研究的另一种策略是将 CustomBaseModel 上找到的属性复制到模型中。但它不起作用。事实上,对于 Pylint 模型来说,它似乎只是一个名字……它忘记了它是什么,也不知道它是一个 class.

如有任何提示,我们将不胜感激...

如果你替换这个:

Model = declarative_base(cls=CustomBaseModel)

像这样:

def base_decorator(cls):
    return declarative_base(cls = cls)

@base_decorator
class Model(CustomBaseModel):
    pass

这将导致类似于以下执行顺序的结果:

class Model(CustomBaseModel):
    pass
Model = declarative_base(cls = Model)

这在功能上与示例代码中的直接调用相同,但它为 pylint 提供了一个线索,即 Model 派生自 CustomBaseModel

这是与我的案例和 SQLAlchemy 最相关的答案,我敢说。感谢 LeoK 为我指明了正确的方向。

如果你像这样重写代码:

from sqlalchemy import as_declarative


@as_declarative
class Model(object):
    # ...
    def feature():
        pass

class Thing(Model):
    pass

t = Thing()
t.feature()  # No more complain !

这将导致与以前完全相同的 Model class,但不需要 中间人 CustomBaseModel class.

由于 class 装饰器预期 return class,这在 Pylint 的意图中更加清晰。它不再丢失 class.

上的属性

请注意,没有什么能阻止您将 class 与装饰器完全混为一谈。尽管 Pylint 可以应付其中的一部分,但并不那么容易被愚弄。我想大多数元编程都会妨碍。

这里有一些可以玩的例子:

def class_breaker(cls):
   # Try some of those:

   # E: 37, 0: Assigning to function call which only returns None (assignment-from-none)
   # return None  # return None too obvious

   # E: 47,21: BrokenClass is not callable (not-callable)
   # cls = None  # Confuses Pylint a bit. Hard to reconcile the message with the issue (IMHO) but correct.
   # return cls

   # No warnings ! return value is a type
   cls = type('Broken', (cls, ), {})
   return cls


@class_breaker
class ClassToBreak(object):
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        """Retrieve the name"""
        return self._name


class OtherClassToBreak(object):
    def __init__(self, name):
        """Init."""
        self._name = name

    @property
    def name(self):
        """Retrieve the name"""
        return self._name


BrokenClass = class_breaker(OtherClassToBreak)


def main():
    instance = ClassToBreak(name='foo')
    print instance.name


    other_instance = BrokenClass(name='foo')
    print other_instance.name


if __name__ == '__main__':
    main()