-
Notifications
You must be signed in to change notification settings - Fork 72
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
Discussion about customizing __init__
or __post_init__
#326
Comments
Thanks for opening this! Apologies for the delayed response, I was on vacation all week. First - Getting to your question, the main issue with supporting overriding Deriving/generating a subset of parameters Sometimes you might want to derive/generate a subset of parameters, avoiding forcing the user to pass them in manually. For example, say we had a right triangle type with lengths for the 3 fields: class RightTriangle(Struct):
"""A right triangle with sides a, b, and c (the hypotenuse)"""
a: float
b: float
c: float You might want to modify the In this situation, I'd recommend using a class RightTriangle(Struct):
"""A right triangle with sides a, b, and c (the hypotenuse)"""
a: float
b: float
c: float
@classmethod
def from_legs(cls, a, b) -> RightTriangle:
return cls(a, b, (a**2 + b**2)**0.5) This keeps it clear with user's intuition that Setting up some extra state Another reason to write your own
The design decision for me is matching user's intuition about what code runs when. I've too often seen python devs confused when their object has invalid state after a If we allow code to run when a
We can't make that guarantee if we let users override
I'd say that |
Thanks a lot for the thoughts! I agree with them all!
This line particularly resonates with me, and I hadn't previously considered it. Thank you!
Yes (though while an evented Struct is important here, i think you'll see that the need for custom The motivating use case for me was with https://github.com/manzt/anywidget. It's a library I've been helping out with that aims to make it easier to bring modern JS modules to target multiple notebook environments (notebooks, jupyter-lab, vscode, colab) without all the boilerplate. While it is currently subclassing ipywidgets (which uses traitlets both for dataclass and observer pattern), we've been working on a new pattern using psygnal and dataclasses on the python side. As you probably know, the main name of the game with jupyter widgets is synchronizing a python-side model with a front-end js model, so ser/des is happening constantly. As such, I thought a msgspec-based model would be an ideal update to the traitlets-based approach. The bare minimum mechanics then becomes: from msgspec import Struct
from anywidget._descriptor import MimeBundleDescriptor
from psygnal import SignalGroupDescriptor
class SomeWidget(Struct):
# whatever fields the user needs to synchronize between front & back end
foo: int = 0
bar: str = 'baz'
# this object implements the observer pattern, allowed callbacks to be connected
# at, for example: SomeWidget().events.foo.connect(my_callback). It also patches __setattr__ 😬
events = SignalGroupDescriptor()
# this object will create a Comm object to communicate with the JS side when a repr is
# requested by a notebook. It would call `msgspec.json.encode(self)`
_repr_mimebundle_ = MimeBundleDescriptor() I can imagine offering that as a base class, in which case an anywidget user would have this lovely from anywidget import AnyWidget
class Switch(AnyWidget):
state: bool = False
_esm: ClassVar = 'some javascript module...' ... and that's where I (rather pre-emptively) imagined that AnyWidget users would say "huh??? I can't customize init at all?" ... even though I personally can't tell you why they would want to 😂. But again, it's all a bit premature.
yep, I'm all for the classmethod approach for offering a customized signature to derive dataclass parameters!
I absolutely respect that approach! in summary: I really appreciate you taking the time to write this out. (I hope you don't mind and I hope it will be a useful thread if the general topic comes up again in the future) |
Thanks for the thoughtful reply. If you ever have a concrete use case for adding |
xref: Potentially resolved in #470 |
Wow! On a quick scan of the related issues, i couldn't quickly find the "killer app" that motivated adding the feature. Can you summarize in a sentence or two? Thanks! |
Description
Hi @jcrist, following up here from https://github.com/davidbrochart/ypywidgets/issues/6#issuecomment-1435227915 ... slightly related to #70, on the general topic of customizing either
__init__
or adding some__post_init__
concept.background
As a bit of context, I'm generally interested in dataclass patterns of all flavors, and enjoy understanding their relative merits and goals; and msgspec seems to be an exciting addition when serialization and speed are top concerns. My interests originally stemmed from my work on napari, where I tried to develop classes & objects that "feel" to the user like relatively simple dataclass-style objects that use some observer pattern to stay in sync with some GUI/view. I extracted that work into psygnal, where I've more recently been adding more generic support to turn any dataclass pattern into an "evented" dataclass that could connect callbacks to changes. I support msgspec (in addition to
dataclasses
,pydantic
, andattrs
)more recently, I've been helping out with anywidget, trying to leverage that pattern to allow widget developers make a python model that stays in sync with a frontend js model, and I'd like to be able to provide modern typing syntax and minimal boilerplate. Given its speed and focus on serialization, it seems that msgspec would be particularly well suited to that ...
customizing
__init__
This issue is therefore part feature request & part discussion. I'd like to encourage developers of relatively simple models to give
msgspec
a try when developing evented models that they want to syncronize with some view – particularly if that view is in some other process that requires serialized communcation. I suspect however – perhaps prematurely – that many developers (i.e. would-be consumers ofpsygnal.evented
oranywidget
) would be flustered a bit to learn that they can't customize initialization of their objects after instance creation, either by overriding__init__
, or by implementing some new__post_init__
.As an example, to support
msgspec
in the first place withpsygnal.evented
, I had to change my internal code to use a descriptor object to get around the fact that I couldn't use__init__
to initialize the event-tracking object for each newStruct
instance. (It so happens I now like that pattern much better 😂 ... but that was how this got on my radar to begin with). Adefault_factory
could conceivably work here too, but only if the factory function was passed the newStruct
instance (I think).question
My question, I suppose, is what would you say to a developer who wanted to use
msgspec.Struct
as their dataclass pattern, maybe even as a superclass base for multiple models in their library. From your experience, do you feel like there's almost always a good alternative to customizing__init__
? Would you say that inasmuch as their classes remain purely data-driven and declarative, then msgspec is ideal, but as they become loaded with more business logic that maybe it's not the use casemsgspec
is targeting? More generally, if we want to do something/anything after the creation of theStruct
instance that isn't just the initialization of a new field, is it possible?as you can see here, I don't have an immediate/personal need to customize
__init__
, it's a bit more of an open "is this really never necessary?" question, when I think about what to suggest to downstream users.thanks for all your work! 🙏
The text was updated successfully, but these errors were encountered: