New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
V2 Generic Improvements #5351
Comments
Generic tests benchmarking: This script was written based on import time
from typing import TypeVar, Generic
from pydantic import BaseModel
def run(n_models: int):
t0 = time.time()
T = TypeVar('T')
C = TypeVar('C')
class A(BaseModel, Generic[T, C]):
x: T
y: C
class B(A[int, C], BaseModel, Generic[C]):
pass
models = []
for i in range(n_models):
class M(BaseModel):
pass
M.__name__ = f'M{i}'
models.append(M)
generics = []
for m in models:
Working = B[m]
generics.append(Working)
t1 = time.time()
print(f"{t1 - t0:.2f} seconds elapsed")
run(3000) On my computer, on commit On the current main branch, I get ~2.40 seconds. (I will do the same check for validation time shortly, though I suspect the impact is smaller.) |
Benchmarking script for validating with generic models: import time
from pathlib import Path
from typing import Generic, TypeVar, Union
from pydantic import BaseModel
T = TypeVar('T')
# Based on tests.test_generics.test_generic_recursive_models
class GenericModel1(BaseModel, Generic[T]):
ref: 'GenericModel2[T]'
model_config = dict(undefined_types_warning=False)
class GenericModel2(BaseModel, Generic[T]):
ref: Union[T, GenericModel1[T]]
model_config = dict(undefined_types_warning=False)
GenericModel1.model_rebuild()
GenericModelStr = GenericModel1[str]
class RecursiveModelStr1(BaseModel):
ref: 'RecursiveModelStr2'
model_config = dict(undefined_types_warning=False)
class RecursiveModelStr2(BaseModel):
ref: Union[str, RecursiveModelStr1]
model_config = dict(undefined_types_warning=False)
RecursiveModelStr1.model_rebuild()
RecursiveModelStr = RecursiveModelStr1
def run1(n_models: int):
t0 = time.time()
for _ in range(n_models):
# RecursiveModelStr.model_validate(dict(ref=dict(ref=dict(ref=dict(ref='123')))))
# GenericModelStr.model_validate(dict(ref=dict(ref=dict(ref=dict(ref='123')))))
GenericModel1[str].model_validate(dict(ref=dict(ref=dict(ref=dict(ref='123')))))
t1 = time.time()
print(f"{t1 - t0:.2f} seconds elapsed")
run1(1000000)
# ~1.89s if you use a non-generic recursive model
# ~1.90s if you use a generic model and cache (i.e., don't re-call __class_getitem__)
# ~2.70s if you use a generic model and don't cache (i.e., re-call __class_getitem__)
# ~1.88s on pre-refactor (commit af73124) if you cache generic
# 3.25s on pre-refactor (commit af73124) if you don't cache generic Good news is that it seems to be highly performant. The |
I'll note that we can get an ~10% speedup in the previous test (where you don't "cache" the parametrized generic type) if we change Putting an Edit: after more investigation, I don't think there's going to be an "easy" way to create a WeakKey version of I will note that we can get a ~2% speedup on |
I found a way to speed things up to only ~10-15% slower than the pre-refactor for building generic models, and all tests pass, but I'm a bit suspicious. @samuelcolvin I think you'll probably know whether the change is actually safe, or maybe we can create a test that fails with the change. I think if it's a safe change though, I'd be happy to consider the performance regression "resolved". There may well still be room for further improvements but I didn't see anything super obvious digging into flamegraphs etc; whereas the regression was obvious (double the number of calls of model_rebuild). |
I am getting a feeling that you are providing minimal support for generics. Why is that? Generics are such an important part of OOP, and my best OOP experience in Python is with My feeling comes from this in the V2 alpha post: Why? Any chances for you to change your mind on this topic? To me it is a no-brainer to just write a test and make sure this never breaks. Could you at least provide the reason for making this decision? Clearly many use generics with Disclaimer I hope you will thoroughly reconsider, and thank you nonetheless. Any news on variadic generics? |
Hi @caniko,
I am disappointed to hear you feel that way, I agree generics are important and have put in a lot of effort to try to improve the experience of using them in v2. My goal is that they feel more reliable/robust in v2, and are even more encouraged for use than they were in v1.
There are a handful of problems with this:
To be clear, we intend to always support If you need to check subclass relationships between two concrete generics, I would suggest checking if their |
Also, re: variadic generics, it would be helpful if you share a unit test (ideally representative of real-world usage) that you'd like to pass, that will make it easier to work on. Might make sense to make a different issue for it and/or open a PR with just the failing unit test. |
Thank you @dmontagu, this was very informative.
I can relax now.
I can make a PR for it when I have downtime. |
I'm going to close this issue and open new issues for handling:
|
TypedDict
to gather theseThe text was updated successfully, but these errors were encountered: