pyglove
pyglove copied to clipboard
Questions about `pg.symbolize`, `pg.members` and compatibility with pyright
I've noticed that we have both pg.symbolize
and pg.members
to do effectively the same thing, symbolizing a class, albeith in different ways. The benefits of pg.symbolize
is that one can use it around a regular class definition and add type hints for all parameters, whereas pg.members
creates all of that under the hood given the list of arguments.
Using pg.members
is nice as one can also provide the documentation for each parameter and express the nested structure of parameters. But it has the shortcoming that typecheckers like pyright cannot know what the types of the parameters are because they are created on the fly by the annotation.
I have two questions:
-
When should we prefer
pg.symbolize
overpg.members
? Most of the usages I've seen ofpg.symbolize
in the notebooks is to wrap around existing classes but it's also handy as an alternative topg.members
as far as I understand? -
What is the story between pyright compatibility in pyglove? I know the dynamic capability of
pg.members
doesn't bode well with typings, but is there a way we could generate pyglove type stubs dynamically? (In this context,ParamSpec
seems relevant.) This way one could usepg.members
and benefit from type checking when using the parameters.
Excellent questions.
- When should we prefer
pg.symbolize
overpg.members
?
A short answer is: use pg.symbolize
when symbolizing an existing class that you cannot modify their source code; and use pg.members
(and subclassing pg.Object
) when you have full control on the class definitions.
Here is a longer version: Both pg.symbolize
and pg.members
w/ subclassing pg.Object
add symbolic programmability to Python classes. pg.symbolize
inject such capabilities into existing classes through multi-inheritance , which tries to avoid clashing with the original classes' behavior (e.g. __eq__
, __hash__
, attribute access) as much as possible. To do so, PyGlove adds minimum modifications to the existing classes's interface by default. On the other hand, we assume that users are mindful and have full control on the class behaviors when they subclass pg.Object
, thus we expose all the symbolic interfaces by default (like <object>.<attr_name>
, symbolic __eq__
, clone
, and etc.).
- What is the story between pyright compatibility in pyglove?
Similar as pytype, pyright does static analysis on the Python files, while PyGlove type check is done at run time. so I don't think pytype/pyright can easily figure out the outcome of pg.members
without handwritten rules (like pytype's special handling for dataclass
). So I guess that we might eventually solve this through the other way around, by making pg.members
more pytype/pyright annotation friendly. I haven't explored this path yet, but it might be possible to converge the difference using dataclass
-like annotation as a bridge.