10

我已经创建了基于 CharField 的自定义 Django 模型字段子类,但它使用 to_python 来确保返回的模型对象具有更复杂的对象(有些是列表,有些是具有特定格式的字典等)——我正在使用 MySQL所以某些 PostGreSql 字段类型不可用。

一切都很好,但 Pylint 认为这些字段中的所有值都是字符串,因此我在使用这些模型的代码上收到很多“不支持的成员资格测试”和“不可订阅的对象”警告。我可以单独禁用这些,但我更愿意让 Pylint 知道这些模型返回某些对象类型。类型提示没有帮助,例如:

class MealPrefs(models.Model):
    user = ...foreign key...
    prefs: dict = custom_fields.DictOfListsExtendsCharField(
            default={'breakfast': ['cereal', 'toast'], 'lunch': []},
            )

我知道某些内置的 Django 字段为 Pylint(CharField,IntegerField)返回正确的类型,并且某些其他扩展已经找到了指定其类型的方法,因此 Pylint 很高兴(MultiSelectField)但是深入研究他们的代码,我无法弄清楚指定返回类型的“魔术”在哪里。

(注意:这个问题与 INPUT:type of Django form fields 无关)

谢谢!

4

2 回答 2

4

出于好奇,我看了一下这个,我认为大多数“魔法”实际上来自pytest-django

在 Django 源代码中,例如 for CharField,没有任何东西可以真正给出类型提示,即这是一个字符串。并且由于该类仅继承自Field,它也是其他非字符串字段的父级,因此需要在其他地方对知识进行编码。

另一方面,通过挖掘 pylint-django 的源代码,我发现这很可能发生在哪里:

在 中pylint_django.transforms.fields,几个字段以类似的方式硬编码:

_STR_FIELDS = ('CharField', 'SlugField', 'URLField', 'TextField', 'EmailField',
               'CommaSeparatedIntegerField', 'FilePathField', 'GenericIPAddressField',
               'IPAddressField', 'RegexField', 'SlugField')

再往下,一个可疑命名的函数apply_type_shim根据字段的类型(“str”、“int”、“dict”、“list”等)向类添加信息。

此附加信息被传递给inference_tip根据 astroid 文档,用于添加推理信息(强调我的):

astroid 不仅可以用作 AST 库,它还提供了一些基本的推理支持,它可以推断名称在给定上下文中的含义,它可以用于解决高度复杂的类层次结构中的属性等。我们称之为这种机制一般会在整个项目中进行推断。

astroid是 Pylint 用来表示 Python 代码的底层库,所以我很确定这就是信息传递给 Pylint 的方式。如果您遵循导入插件时发生的情况,您会在pylint_django/.plugin中发现这个有趣的部分,它实际上是在其中导入transforms,有效地将推理提示添加到 AST 节点。

我认为,如果您想通过自己的课程实现相同的目标,您可以:

  1. 直接从另一个已经具有您正在寻找的关联类型的 Django 模型类派生。
  2. 创建并注册一个等效的 pylint 插件,该插件也将使用 Astroid 向类添加信息,以便 Pylint 知道如何处理它。
于 2019-03-26T23:51:18.257 回答
4

我最初以为您使用了一个插件pylint-django,但也许您明确使用了发现 Django 时会自动安装 pylint-django 的prospector

检查器pylint和它的插件都不会通过 Python 类型注释 ( PEP 484 ) 中的使用信息来检查代码。它可以在不理解注释的情况下解析带有注释的代码,例如,如果名称仅在注释中使用,则不会警告“未使用的导入”。如果该类没有方法,则该消息unsupported-membership-test将在带有表达式的行中报告。同样,消息与方法有关。something in object_AA()__contains__unsubscriptable-object__getitem__


您可以通过这种方式为您的自定义字段修补pylint-django
: 添加一个函数:

def my_apply_type_shim(cls, _context=None):  # noqa
    if cls.name == 'MyListField':
        base_nodes = scoped_nodes.builtin_lookup('list')
    elif cls.name == 'MyDictField':
        base_nodes = scoped_nodes.builtin_lookup('dict')
    else:
        return apply_type_shim(cls, _context)
    base_nodes = [n for n in base_nodes[1] if not isinstance(n, nodes.ImportFrom)]
    return iter([cls] + base_nodes)

进入pylint_django/transforms/fields.py

并在此行的同一文件中替换apply_type_shim为:my_apply_type_shim

def add_transforms(manager):
    manager.register_transform(nodes.ClassDef, inference_tip(my_apply_type_shim), is_model_or_form_field)

Model如果它们在 a或中使用,这会分别将基类 list 或 dict 及其魔术方法添加到您的自定义字段类中FormView


笔记:

我还考虑了一个插件存根解决方案,它做同样的事情,但是“prospector”的替代方案对于 SO 来说似乎太复杂了,我更喜欢在安装后简单地修补源代码。

类 Model 或 FormView 是元类创建的唯一类,在 Django 中使用。通过插件代码模拟元类并控制分析简单属性是一个好主意。如果我记得的话,这里的一些评论中引用的MyPy也有一个适用于 Django 的插件 mypy-django,但仅适用于 FormView,因为为 for 编写注释django.db比使用属性更复杂。- 我试图为它工作一个星期。

于 2019-03-27T18:22:34.740 回答