是否可以使用Python类型提示在Django QuerySet中指定记录类型?像QuerySet[SomeModel]
?
例如,我们有模型:
class SomeModel(models.Model):
smth = models.IntegerField()
我们希望将该模型的QuerySet作为参数传递给func:
def somefunc(rows: QuerySet):
pass
但是如何在QuerySet中指定记录类型,例如List[SomeModel]
:
def somefunc(rows: List[SomeModel]):
pass
但是使用QuerySet?
答案 0 :(得分:28)
一种解决方案可能是使用联盟打字类。
from typing import Union, List
from django.db.models import QuerySet
from my_app.models import MyModel
def somefunc(row: Union[QuerySet, List[MyModel]]):
pass
现在,当您对row
参数进行切片时,它将知道返回的类型是另一个MyModel列表或MyModel的实例,同时还暗示QuerySet
类的方法可用于row
参数也是。{/ p>
答案 1 :(得分:4)
我制作了此帮助器类以获取通用类型提示:
def somefunc(row: ModelType[SomeModel]):
pass
然后像这样使用它:
ModelType[DifferentModel]
这会减少我每次使用此类型时的噪音,并使其在模型之间可用(例如def middle(word):
return word[1:-1]
def is_palindrome(word):
if word[0] != word[-1]:
return False
elif len(word)<=1:
return True
else:
return is_palindrome(middle(word))
)。
答案 2 :(得分:4)
有一个名为routes()
(名称为follows PEP561
)的特殊程序包,用于键入您的django-stubs
代码。
它是这样工作的:
django
输出:
# server/apps/main/views.py
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
def index(request: HttpRequest) -> HttpResponse:
reveal_type(request.is_ajax)
reveal_type(request.user)
return render(request, 'main/index.html')
以及模型和» PYTHONPATH="$PYTHONPATH:$PWD" mypy server
server/apps/main/views.py:14: note: Revealed type is 'def () -> builtins.bool'
server/apps/main/views.py:15: note: Revealed type is 'django.contrib.auth.models.User'
:
QuerySet
输出:
# server/apps/main/logic/repo.py
from django.db.models.query import QuerySet
from server.apps.main.models import BlogPost
def published_posts() -> 'QuerySet[BlogPost]': # works fine!
return BlogPost.objects.filter(
is_published=True,
)
reveal_type(published_posts().first())
# => Union[server.apps.main.models.BlogPost*, None]
的类型:https://github.com/typeddjango/django-stubs django
的类型:https://github.com/typeddjango/djangorestframework-stubs 答案 3 :(得分:2)
我也正在寻找解决方案。 Django开发人员列表中有thread,他们正在讨论如何实现这样的功能。
他们目前正在开发Django extension to mypy,但看起来我们可能因为我们的具体要求而运气不好。在他们的路线图中的标题是&#34;可能从不&#34;:
Queryset可能有一些部分支持,但复杂的参数(如 过滤和获取查询的那些)或Q和F对象超出了 mypy现在的表达可能性。
说明,在条件改善之前,我们必须使用普通的QuerySet
。
答案 4 :(得分:1)
QuerySet
是返回任何模型的任何查询集的函数/方法的好方法。 Django 查询集是可迭代的。但是当返回类型非常特定于一种模型时,最好使用 QuerySet[Model]
而不是 QuerySet
。
示例:过滤公司的所有活跃用户
import datetime
from django.utils import timezone
from myapp.models import User
from collections.abc import Iterable
def get_active_users(company_id: int) -> QuerySet[User]:
one_month_ago = (timezone.now() - datetime.timedelta(days=30)).timestamp()
return User.objects.filter(company_id=company_id, is_active=True,
last_seen__gte=one_month_ago)
上面的函数签名比def get_active_users(company_id: int) -> QuerySet:
更具可读性
Iterable[User]
也可以工作,类型检查器会在其他方法上调用返回的查询集时抱怨。
def func() -> Iterable[User]:
return User.objects.all()
users = func()
users.filter(email__startswith='support')
MyPy 输出
"Iterable[User]" has no attribute "filter"
答案 5 :(得分:0)
这是Or Duan的改进的辅助类。
from django.db.models import QuerySet
from typing import Iterator, TypeVar, Generic
_Z = TypeVar("_Z")
class QueryType(Generic[_Z], QuerySet):
def __iter__(self) -> Iterator[_Z]: ...
此类专门用于QuerySet
对象,例如在查询中使用filter
时。
样本:
from some_file import QueryType
sample_query: QueryType[SampleClass] = SampleClass.objects.filter(name=name)
现在解释器将sample_query
识别为QuerySet
对象,您将获得诸如count()
之类的建议,并且在遍历这些对象时,您将获得针对{{1} }
注意
从SampleClass
起可以使用这种类型的提示格式。
答案 6 :(得分:0)
我发现自己使用typing.Sequence
解决了类似的问题:
from typing import Sequence
def print_emails(users: Sequence[User]):
for user in users:
print(user.email)
users = User.objects.all()
print_emails(users=users)
据我从文档中了解到的
序列是支持len()和。 getitem ()的任何东西,而与它的实际类型无关。
答案 7 :(得分:0)
from typing import Iterable
def func(queryset_or_list: Iterable[MyModel]):
pass
queryset和模型实例列表都是可迭代对象。
答案 8 :(得分:0)
from typing import (TypeVar, Generic, Iterable, Optional)
from django.db.models import Model
from django.db.models import QuerySet
_T = TypeVar("_T", bound=Model)
class QuerySetType(Generic[_T], QuerySet):
def __iter__(self) -> Iterable[_T]:
pass
def first(self) -> Optional[_T]:
pass
答案 9 :(得分:-1)
如果导入注释模块,您实际上可以做您想做的事情:
from __future__ import annotations
from django.db import models
from django.db.models.query import QuerySet
class MyModel(models.Model):
pass
def my_function() -> QuerySet[MyModel]:
return MyModel.objects.all()
MyPy和Python解释器都不会对此进行抱怨或引发异常(在python 3.7上进行了测试)。 MyPy可能无法对其进行类型检查,但是如果您只想记录您的返回类型,这应该就足够了。
答案 10 :(得分:-1)
恕我直言,正确的方法是定义一个继承QuerySet
的类型,并为迭代器指定一个通用的返回类型。
from django.db.models import QuerySet
from typing import Iterator, TypeVar, Generic, Optional
T = TypeVar("T")
class QuerySetType(Generic[T], QuerySet): # QuerySet + Iterator
def __iter__(self) -> Iterator[T]:
pass
def first(self) -> Optional[T]:
pass
# ... add more refinements
然后您可以像这样使用它:
users: QuerySetType[User] = User.objects.all()
for user in users:
print(user.email) # typing OK!
user = users.first() # typing OK!
答案 11 :(得分:-14)
如果你想通过一个参数,就这样做:
def somefunc(smth):
pass
如果您想返回列表类型,请使用函数list()
。