【问题标题】:Specify typing for Django field in model (for Pylint)为模型中的 Django 字段指定类型(用于 Pylint)
【发布时间】:2019-06-18 12:35:32
【问题描述】:

我已经创建了基于 CharField 的自定义 Django 模型字段子类,但它使用 to_python 来确保返回的模型对象具有更复杂的对象(有些是列表,有些是具有特定格式的字典等)——我' m 使用 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)但深入研究他们的代码,我可以' t找出指定返回类型的“魔法”在哪里。

(注意:这个问题与 Django 表单字段的 INPUT:type 无关)

谢谢!

【问题讨论】:

  • 咨询了stackoverflow.com/questions/51830993/…,但没有重复或指定答案。
  • 警告来自哪里的代码?您是否愿意在更高级别禁用任何不需要的 pylint 警告?或者您是否想通知 pylint 期望返回类型,然后在出现意外情况时捕获。在这种情况下,我通常只是在更高级别禁用:-)
  • 我可以在更高级别禁用,但我试图了解是什么让 pylint 了解内置 Django 字段的正确类型,但不是我创建的字段。
  • 您可能应该添加您使用插件pylint-django 的信息。 (或者不是?)
  • 我不知道这个插件。如果您将其写为答案,将这样做并乐意奖励赏金@hynekcer。但是即使没有插件,某些代码也能正常工作

标签: django python-3.x django-models pylint python-typing


【解决方案1】:

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

在 Django 源代码中,例如对于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,其中according to the astroid docs 用于添加推理信息(强调我的):

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

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

我认为,如果您想通过自己的课程达到同样的效果,您可以:

  1. 直接派生自另一个 Django 模型类,该模型类已经具有您要查找的关联类型。
  2. 创建并注册一个等效的 pylint 插件,该插件还将使用 Astroid 向类添加信息,以便 Pylint 知道如何处理它。

【讨论】:

  • 感谢您的出色回答。
【解决方案2】:

我最初以为你使用了一个插件pylint-django,但也许你明确使用了prospector,它会在找到 Django 时自动安装 pylint-django。

检查器pylint 和它的插件都不会通过来自 Python 类型注释 (PEP 484) 的使用信息来检查代码。它可以在不理解注释的情况下解析带有注释的代码,例如如果名称仅在注释中使用,则不要警告“未使用的导入”。如果类A() 没有方法__contains__,则消息unsupported-membership-test 在表达式something in object_A 的行中报告。同样,消息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)

如果它们在ModelFormView 中使用,这会分别将基类列表或字典以及上面解释的魔术方法添加到您的自定义字段类中。


注意事项:

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

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

【讨论】:

  • 我正在使用 PyCharm 的内置类型检查器,所以我不完全确定它在后台使用的是什么。
猜你喜欢
  • 1970-01-01
  • 2014-11-11
  • 2011-04-28
  • 2015-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-29
  • 2020-12-10
相关资源
最近更新 更多