Allow VGroup type subscripting - #3606
JasonGrace2282 wants to merge 25 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
|
What are you trying to achieve with this feature? |
I agree that would be the best solution, but I wanted to avoid (if possible) having to manually make every class a
Oh I didn't know that, thanks for letting me know :) |
|
Having class VGroup(VMobject, Generic[T], metaclass=ConvertToOpenGL):
def __init__(self, *vmobjects: T, **kwargs):
super().__init__(**kwargs)
self.add(*vmobjects)But it would imply
Not sure I understood this part, could you please give an example? |
It is not in fact the case, but maybe something like
Sure, ideally we would only change what class VGroup(VMobject, Generic[T], metaclass=ConvertToOpenGL):
def __init__(self, *vmobjects: T, **kwargs):
super().__init__(**kwargs)
self.add(*vmobjects)
class Subclass1(VGroup):
pass
class Subclass2(VGroup[T]):
pass
Subclass1[VMobject] # error
Subclass2[VMobject] # no errorIt was just me being lazy and not wanting to have to create a |
|
I see. While this might look cumbersome to add the type variable to every subclass, this is required for type checkers to understand the generic class. Adding a bare In my opinion, the type variable should only be added to
You can do something like: VMobjectT = TypeVar("VMobjectT", bound=VMobject)
class VGroup(VMobject, Generic[VMobjectT], metaclass=ConvertToOpenGL):
def __init__(self, *vmobjects: VMobjectT, **kwargs): ...That way, users that use different vmobjects in a vgroup won't be impacted: g = VGroup(DashedVMobject(), DashedVMobject())
reveal_type(g) # VGroup[DashedVMobject()]
g = VGroup(DashedVMobject(), Triangle())
reveal_type(g) # "VGroup[DashedVMobject | Triangle]" for pyright | "VGroup[VMobject]" for mypyWe could also make use of PEP 696 in this case. It is still in draft but will most probably make it: from typing_extensions import TypeVar
VMobjectT = TypeVar("VMobjectT", bound=VMobject, default=VMobject)That way a plain As you can see, it is not perfect as mypy and pyright handle unions differently. But that could still work |
|
And I assume all subclasses would be subclassing class ManimBanner(VGroup[VMobject]):
... |
If the subclass behaves the same way |
.Mobject.__class_getitem__ to allow type subscripting|
Seeing as PEP 696 was accepted I've gone ahead and made the changes, let me know what you think! |
9caa588 to
99c3241
Compare
|
this week I'll have time to review your typing related PRs. |
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Sorry for the long wait. LGTM!
There are still a lot of places where we could subscript VGroup such as matrix.py, table.py or coordinate_systems.py, but the changes are too big and could be done in one or more subsequent PRs.
EDIT: I pushed a small change such that VGroup.__getitem__() now returns a VMobjectT | VGroup[VMobjectT]. This allows for things such as:
lines: VGroup[Line] = VGroup(Line() for _ in range(10))
lines[0].get_unit_vector() # lines[0] is detected as a Line and this method can be autocompleted
some_lines = lines[:3] # some_lines is detected as a VGroup[Line]
some_lines[0].get_unit_vector() # therefore, some_lines[0] is also detected as a Line|
|
||
| def __getitem__(self, key: int | slice) -> VMobject: | ||
| @overload | ||
| def __getitem__(self, key: int) -> VMobjectT: ... |
| def __getitem__(self, key: int) -> VMobjectT: ... | ||
|
|
||
| @overload | ||
| def __getitem__(self, key: slice) -> VGroup[VMobjectT]: ... |
|
Can I ask what the benefit of doing this might be? Unless I'm missing something, I don't see any discussion anywhere in this thread of what problem it solves or how people are intended to use this new typing feature. |
| VMobjectT = TypeVar("VMobjectT", bound=VMobject, default=VMobject) | ||
|
|
||
|
|
||
| class VGroup(VMobject, Generic[VMobjectT], metaclass=ConvertToOpenGL): |
There was a problem hiding this comment.
Is there a reason for Mobject or VMobject not to be the generic class instead?
There was a problem hiding this comment.
The first iterations of this PR had Mobject as the generic class, but it was later changed to only VGroup. It seems the reason is that having Mobject as a generic implied modifying every single subclass of Mobject and it would've been too cumbersome to implement. Viicos suggested making only VGroup generic, saying that it's not really common to deal with the type variable for Mobject submobjects. Jason could explain it better, though.
Some Mobjects have attributes which are
Allowing |
|
@chopan050: That makes a lot of sense, at least on the face of it! The I do have one concern which I hope we can go over: IMO, one of the big benefits of Manim is that everything(-ish) is a mobject and can be modified using the same mobject semantics. Could this feature potentially risk limiting this freedom for the end-user? For example, I could easily see a user do one or more of the following: ax = Axes(*some_args)
# Let's add a background to the axes for some reason
ax.axes.add(BackgroundRectangle(ax.axes, color=BLACK))matrix = Matrix(*some_args, element_to_mobject=Text) # matrix.elements is a VGroup[Text]
# Let's replace the 5th element with an image of a funny meme
current_element = matrix.elements[4]
matrix.elements[4] = ImageMobject("my_image.png").set(height=current_element)# Let's import and use a hypothetical mobject from a plugin. We'll say it has type VGroup[Circle]
from some_plugin import DiskIntersection
disks = DiskIntersection(*some_args)
# For some cool math reason, we can treat some of these disks as squares, so let's add those
disks.add(*[Square().move_to(pos) for pos in square_positions])Assuming the user is using mypy, what kind of errors would they be getting by doing the above things? Warnings would probably be okay, but I'm personally wary of flagging any "valid" userland code with an error just because it doesn't match an internal type assumption. It's possible that there's an easy workaround that users would be expected to use, but I think my knowledge of mypy and generics isn't strong enough here. To put it differently: it seems like this feature runs the risk of creating a class of Manim-native mobjects which deliberately restrict their range of "permitted" interactions in a way that appears somewhat arbitrary to the user ("why shouldn't I be able to insert a funny meme in my matrix?"). |
Allows things like
VGroup[Rectangle][v1]
Adds a
__class_getitem__method toMobjectThis was not useful, as discussed later
[v2]
Make
VGroupa genericGuideline
Since
VGroup's are invariant, typeVGroup's similar to lists. For example:Correct:
Incorrect:
Also renders correctly in the docs, ex LinearTransformationScene.get_ghost_vectors