Skip to content

Attrs - Initial progress #271

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

Closed
wants to merge 211 commits into from
Closed

Conversation

azarzadavila
Copy link
Contributor

As discussed in #7 @PgBiel and @kilacoda started working on progressively ditching CONFIG to use instead attrs.
By conversing with PgBiel I thought I could give a bit of help. This PR is currently more a way to see and discuss how attrs could look like in the project.
I would also like to bring a few issues with using this approach.

List of Changes

  • Mobject using attrs
  • geometry.py using attrs
  • test_geometry.py compatible with the changes

Issues with attrs

attrs is not really inheritance friendly. As manim abundantly uses inheritance this results in a few issues/shortcomings.

  • MAIN ISSUE A class that inherits from a class that possesses a keyword argument for initialization can not have a positional argument.
    This means for example that if TipableVMobject uses attrs and has a keyword argument than Line can not use start and end as positional arguments.
  • One advantage of using attrs is to specify a type to each attribute. However if one wants to redefine the default value for a parent attribute, than the type of said attribute must be re-specified. For example, Circle that uses RED as default subclasses Arc that uses WHITE as default. Both classes must specify the type of arg color. It could look like this :
class Arc(TipableVMobject):
    color: typing.Union[str, Color] = WHITE

class Circle(Arc):
    color: typing.Union[str, Color] = RED

Thus repeating typing.Union[str, Color].

Possible solutions

  • attrs suggests the use of @classmethod for some initialization.
    This could be used for cases where the arguments in the master __init__ version uses positional arguments and/or the passed arguments should not be attributes of the class. For example a method for Line could be included :
    @classmethod
    def from_start_end(cls, start, end, **kwargs):
        return cls(start=start, end=end, **kwargs)
  • a special validator could be used to indicate some initialization arguments can not be None.
    This would still require the use of keyword arguments but it would ensure that they are passed anyway.

These solutions would still break code that includes positional arguments.

Testing Status

It passes two tests :

  • test_creation.py
  • test_geometry.py which has been modified to remove positional arguments.

TODO

  • find a way to still keep CONFIG for backward compatibility reasons. This should not be too hard, I believe it is just a matter of placing an adequate function in __attrs_post_init__.
  • choose adequate types for all attributes
  • implement adequate validators
  • further replace CONFIG in other parts of the code

Further Comments

A custom decorator dclass was generated to not repeat what was chosen as parameters in the attrs decorator :

dclass = functools.partial(attr.s, auto_attribs=True, eq=False)

(slots=True will normally also be part of final implementation).
Custom decorators like this are not analyzed by PyCharm this means the attributes are not part of the autocompletion.
However attr.s is recognized by PyCharm (I don't know for other IDEs) so I think I'll prefer to rewrite each time the decorator.

I hope this will bring more insight on how attrs could be integrated to manim and I'll try to keep working on this PR this week.

Acknowledgement

Copy link
Member

@PgBiel PgBiel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's just way too much for me to review right now... also the master merge is kinda annoying while reviewing ngl but i'll let it be for now

apply my comments so far to all files. In general, the float attributes are being set to ints: either set them to .0 or Union[int, float]. Also, unnecessary tp.Anys are being used. Finally, we have to specify the types of elements in lists!

# If None, this defaults to the sum of all
# internal animations
run_time: float = None
rate_func: tp.Any = linear
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

Suggested change
rate_func: tp.Any = linear
rate_func: typing.Union[typing.Callable[[float, float], float], typing.Callable[[float], float]] \
= linear

# If >0 and <1, they start at lagged times
# from one and other.
lag_ratio: float = 0
group: tp.Any = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???
(Note: normally this would be Optional[Group], but since on init it is always set to a Group, then it's ok to have it as None)

Suggested change
group: tp.Any = None
group: Group = None

# from one and other.
lag_ratio: float = 0
group: tp.Any = None
animations: tp.List = attr.ib(default=attr.Factory(list))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list of what? also just use this

Suggested change
animations: tp.List = attr.ib(default=attr.Factory(list))
animations: tp.List[Animation] = attr.ib(factory=list)

class Uncreate(ShowCreation):
CONFIG = {"rate_func": lambda t: smooth(1 - t), "remover": True}
rate_func: tp.Any = lambda t: smooth(1 - t)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rate_func: tp.Any = lambda t: smooth(1 - t)
rate_func: typing.Union[typing.Callable[[float, float], float], typing.Callable[[float], float]] \
= lambda t: smooth(1 - t)

"scale_factor": 1.2,
"color": YELLOW,
}
rate_func: tp.Any = there_and_back
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rate_func: tp.Any = there_and_back
rate_func: typing.Union[typing.Callable[[float, float], float], typing.Callable[[float], float]] \
= there_and_back

"color": WHITE,
}
radius: float = DEFAULT_DOT_RADIUS
stroke_width: float = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that or change to 0.0

Suggested change
stroke_width: float = 0
stroke_width: tp.Union[int, float] = 0
Suggested change
stroke_width: float = 0
stroke_width: float = 0.0

inner_radius: float = 1.0
outer_radius: float = 2.0
angle: float = TAU / 4
start_angle: float = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that or change to 0.0

Suggested change
start_angle: float = 0
start_angle: tp.Union[int, float] = 0
Suggested change
start_angle: float = 0
start_angle: float = 0.0

"buff": 0,
"path_arc": None, # angle of arc specified here
}
buff: float = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
buff: float = 0
buff: tp.Union[int, float] = 0
Suggested change
buff: float = 0
buff: float = 0.0

"angle": 0,
}
width: float = 0.2
angle: float = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
angle: float = 0
angle: tp.Union[int, float] = 0
Suggested change
angle: float = 0
angle: float = 0.0

stroke_width: float = 6.0
buff: float = MED_SMALL_BUFF
max_tip_length_to_length_ratio: float = 0.25
max_stroke_width_to_length_ratio: float = 5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
max_stroke_width_to_length_ratio: float = 5
max_stroke_width_to_length_ratio: tp.Union[int, float] = 5
Suggested change
max_stroke_width_to_length_ratio: float = 5
max_stroke_width_to_length_ratio: float = 5.0

def __init__(self, **kwargs):
VGroup.__init__(self, **kwargs)
def __attrs_post_init__(self):
VGroup.__attrs_post_init__(self)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: why not use super()?

self.x_axis = self.create_axis(self.x_min, self.x_max, self.x_axis_config)
self.y_axis = self.create_axis(self.y_min, self.y_max, self.y_axis_config)
self.y_axis.rotate(90 * DEGREES, about_point=ORIGIN)
# Add as a separate group incase various other
# mobjects are added to self, as for example in
# NumberPlane below
self.axes = VGroup(self.x_axis, self.y_axis)
self.axes = VGroup.from_vmobjects(self.x_axis, self.y_axis)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If VGroup.add is made to perform input checking, then VGroup.from_vmobjects() can become VGroup(), yes?

class CurvedArrow(ArcBetweenPoints):
def __init__(self, start_point, end_point, **kwargs):
ArcBetweenPoints.__init__(self, start_point, end_point, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think super() can be weird when multiple inheritance is involved. In other cases, it's fine.

@leotrs
Copy link
Contributor

leotrs commented Aug 16, 2020

This PR is way too large to review effectively. In the future we should make smaller, incremental, self-contained PRs in general

@PgBiel
Copy link
Member

PgBiel commented Aug 16, 2020

In the future we should make smaller, incremental, self-contained PRs in general

In fact, I think we should start immediately. Our plan was to split our work in attrs; I proposed doing one PR per folder, while EulerTour proposed a further division. We can't work with a PR for everything. So I propose we split this here and now

@leotrs
Copy link
Contributor

leotrs commented Aug 16, 2020

Yep, let's do it. 251 files in a single PR is unmanageable I think.

@azarzadavila
Copy link
Contributor Author

Okey, sorry I was not sure how to proceed with this PR. I also thought merging master would look nicer than just show all commits since beginning of the PR. I wanted to be able to pass all tests before putting this ready to review. How were you thinking of proceeding? Perhaps it would be interesting to keep a separate branch and applying multiple PRs to that branch until it has at least everything to pass the tests. For example, I think it was necessary once Mobject pass to attrs every Mobject subclass should also use attrs to avoid any errors.

@leotrs
Copy link
Contributor

leotrs commented Aug 16, 2020

Whoever is working on the attrs branch should periodically merge master into attrs (NOT the other way around). And attrs should
be merged into master frequently once each file or folder is done.

@leotrs
Copy link
Contributor

leotrs commented Aug 16, 2020

I think the way forward here is to delete this PR, merge master into attrs and resolve merge conflicts, and PR from attrs again, only a few files at a time if possible.

@PgBiel
Copy link
Member

PgBiel commented Aug 16, 2020

Okey, sorry I was not sure how to proceed with this PR.

It's perfectly fine! Sorry if it sounded like some sort of rant or something lol, I should have expressed myself better... Just, it needed to be mentioned

I also thought merging master would look nicer than just show all commits since beginning of the PR.

We can prob do this in the attrs branch as leo proposed above

I wanted to be able to pass all tests before putting this ready to review.

Well, that's certainly an issue: by splitting PRs, tests won't pass just yet. But once we build the attrs branch slowly (see below), at the end, we will be able to verify tests with the "big PR".

How were you thinking of proceeding?

(see below)

Perhaps it would be interesting to keep a separate branch and applying multiple PRs to that branch until it has at least everything to pass the tests.

The idea is similar: we'd go merging PRs until we finish the job, then we'd make one master PR (haha get it) (ok sorry that was bad) to concretize the changes

For example, I think it was necessary once Mobject pass to attrs every Mobject subclass should also use attrs to avoid any errors.

Doing this would generate necessarily big PRs. We can't depend on the inheritance tree. There are better options, such as per folder or idk 2 per folder, gotta see that

@PgBiel
Copy link
Member

PgBiel commented Aug 16, 2020

I think the way forward here is to delete this PR, merge master into attrs and resolve merge conflicts, and PR from attrs again, only a few files at a time if possible.

sure thing bud

@leotrs
Copy link
Contributor

leotrs commented Aug 23, 2020

@azarzadavila @PgBiel

As per the comments here, it doesn't seem like we are going to move forward with this PR, right? It is my understanding that this will be broken down into several smaller PRs. If that's the case, please close this one, else please update on the status.

@eulertour eulertour mentioned this pull request Oct 4, 2020
@leotrs
Copy link
Contributor

leotrs commented Oct 8, 2020

I'm closing this for lack of activity.

@leotrs leotrs closed this Oct 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Additions and improvements in general refactor Refactor or redesign of existing code
Projects
None yet
Development

Successfully merging this pull request may close these issues.