Python Astroid (Pylint) Как имитировать наследование, когда класс проходит через фабрику? - PullRequest
0 голосов
/ 16 сентября 2018

Там, где я работаю, мы широко используем SQLAlchemy.С течением времени мы разработали базовый класс для наших моделей, который соответствует нашим потребностям.Но когда приходит время помешать нашему коду, мы всегда ошеломлены предупреждением о том, что можем игнорировать.Но пока нам удалось сделать это глобально только с директивой generated-members, которая имеет тенденцию скрывать проблемы.Поэтому я начал задаваться вопросом: «Как я могу научить это пилинту?»

Вот ситуация:

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.

Следовательно, похоже, я должен использовать inference_tip в возвращаемом значении вызова declarative_base().Но я не совсем уверен, как поступить.И, похоже, API менялся с течением времени, и я никуда не уйду.

Другая стратегия, на которую я обратил внимание, - это копирование атрибутов, найденных в CustomBaseModel, в Model.Но это не работает.Действительно, Pylint Model кажется просто именем ... она потеряла представление о том, что это такое, и не имеет ни малейшего представления, что это класс.

Любые намеки будут высоко оценены ...

Ответы [ 2 ]

0 голосов
/ 16 сентября 2018

Вот ответ, наиболее актуальный для моего случая и 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 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()
0 голосов
/ 16 сентября 2018

Если вы замените это:

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.

...