Added support for iterables in VGroup constructor#3686
Added support for iterables in VGroup constructor#3686oscjac wants to merge 6 commits intoManimCommunity:mainfrom
Conversation
db7b4a6 to
19a6a38
Compare
JasonGrace2282
left a comment
There was a problem hiding this comment.
Hey, sorry for the late review. I've left some comments regarding what I noticed, feel free to take a look when you find the time!
Thanks for making manim better ✨
|
|
||
| import itertools as it | ||
| import sys | ||
| from types import GeneratorType |
There was a problem hiding this comment.
I would prefer this be under if TYPE_CHECKING since generators are counted as iterables
| temp = [] | ||
| temp.extend(m) |
There was a problem hiding this comment.
| temp = [] | |
| temp.extend(m) | |
| temp = tuple(m) |
| # Mobject and its subclasses are iterable | ||
| if isinstance(m, (Iterable, GeneratorType)): | ||
| # If it's not a subclass of Mobject or OpenGLMobject, it must be an iterable or generator | ||
| if not isinstance(m, (Mobject, OpenGLMobject)): |
There was a problem hiding this comment.
You could rewrite this in a way that reduces indentation by just checking if it's a Mobject before checking if it's an iterable.
| if not isinstance(t, (VMobject, OpenGLVMobject)): | ||
| raise TypeError( | ||
| f"All submobjects of {self.__class__.__name__} must be of type VMobject. " | ||
| f"Got {repr(m)} ({type(m).__name__}) instead. " |
There was a problem hiding this comment.
| f"Got {repr(m)} ({type(m).__name__}) instead. " | |
| f"Got {repr(t)} ({type(t).__name__}) instead. " |
Copy-paste error?
| def test_vgroup_iter_init(): | ||
| """Test the VGroup instantiation with an iterable type.""" | ||
|
|
||
| def basic_generator(n, type): |
There was a problem hiding this comment.
I would suggest using a different parameter name other than type (since it's a built-in function/metaclass/whatever you want to call it).
If you really think type is the best name, use the name type_ as that's the naming standard for python.
| i = 0 | ||
| while i < n: | ||
| i += 1 | ||
| yield type() |
There was a problem hiding this comment.
Why not something like this?
| i = 0 | |
| while i < n: | |
| i += 1 | |
| yield type() | |
| for _ in range(n): | |
| yield type() |
| obj = VGroup(basic_generator(5, VMobject)) | ||
| assert len(obj.submobjects) == 5 | ||
| with pytest.raises(TypeError): | ||
| VGroup(basic_generator(Mobject)) |
There was a problem hiding this comment.
| VGroup(basic_generator(Mobject)) | |
| VGroup(basic_generator(1, Mobject)) |
Is this what you meant?
| VGroup(OpenGLMobject(), OpenGLMobject()) | ||
|
|
||
|
|
||
| def test_vgroup_iter_init(using_opengl_renderer): |
| for m in vmobjects: | ||
| if not isinstance(m, (VMobject, OpenGLVMobject)): | ||
| # Mobject and its subclasses are iterable | ||
| if not isinstance(m, (Iterable, Generator)): |
There was a problem hiding this comment.
Perhaps I may not have been clear enough in my last review, but you could reduce the amount of explanation needed and the clarity of the code by doing the following:
if isinstance(mob, VMobject): # first
...
elif isinstance(mob, Iterable): # generator not needed, it's a subprotocol of Iterable
for submob in mob:
if not isinstance(submob, VMobject):
raise
flattened.extend(mob)
else:
raiseThe benefit of this is that it's easier to read and it avoids repeating the same text for the ValueError in 3 places (it repeats it twice, in the ideal case it should never repeat it)
There was a problem hiding this comment.
I see, thank you for the reviews! One problem that I'm running into testing this change locally is that when the constructor is passed an Mobject instance, it does not throw a TypeError.
The test is located in (tests/ ... /test_vectorized_mobject.py:77).
These are the changes I'm testing:
if isinstance(mob, VMobject):
flattened_args.append(mob)
elif isinstance(mob, Iterable):
for submob in mob:
if not isinstance(submob, VMobject):
raise TypeError(...)
flattened_args.extend(mob)
else:
raise TypeError(...)The problem is that an empty Mobject is iterable but, since it is empty, it has no submobs and so the constructor never throws a TypeError. My first thought was to add another check before iterating through the submob as such:
elif isinstance(mob, Iterable):
if (mob, is instance(Mobject)):
raise TypeError(...)
for submob in mob:
if not isinstance(submob, VMobject):
raise TypeError(...)
flattened_args.extend(mob)but, going off what you've already mentioned, I take it that it's not the best solution because it adds yet another copy of the TypeError and it is less readable. How do you think we could solve this without degrading the code's readability?
There was a problem hiding this comment.
Nice catch, didn't think about that. It could be circumvented with something like this
if isinstance(mob, VMobject):
...
elif isinstance(mob, Iterable) and type(mob) is not Mobject:
for submob in mob:
if not isinstance(submob, VMobject):
raise
flattened.extend(mob)
else:
raiseNot as clean as I like, but it should keep only two copies of the error message.
I would also suggest putting the error message into a function and simply call that function both times to reduce the repetition of the error.
Thanks for your patience :)
|
Closing due to inactivity - please reopen if this is not the case! |
Overview: What does this pull request change?
Also includes tests to verify that the change works as intended. This change does not appear to affect other existing tests, other than adding a negligible amount of runtime cost to calling the Vgroup constructor. Closes #3540
Reviewer Checklist