当前位置: 代码迷 >> python >> Python Astroid (Pylint) 如何在类通过工厂时模拟继承?
  详细解决方案

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

热度:74   发布时间:2023-06-19 09:14:50.0

在我工作的地方,我们广泛使用 SQLAlchemy。 随着时间的推移,我们为我们的模型开发了一个适合我们需要的基类。 但是当需要对我们的代码进行 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 Model 实际上或多或少是CustomBaseModel。

因此,它看起来更像是我应该对declarative_base()调用的返回值使用 。 但我不确定如何进行。 看起来 API 会随着时间而改变,我不会去任何地方。

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

任何提示将不胜感激...

如果你替换这个:

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类,但不需要中间的CustomBaseModel类。

由于类装饰器应返回一个类,因此 Pylint 的意图更明确。 它不再失去对类属性的跟踪。

请注意,没有什么可以阻止您将类与装饰器完全混淆。 虽然 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()