Closed liamhuber closed 2 months ago
Check out this pull request on
See visual diffs & provide feedback on Jupyter Notebooks.
Powered by ReviewNB
Coverage variation | Diff coverage |
---|---|
:white_check_mark: +0.71% (target: -1.00%) | :white_check_mark: 97.73% |
Codacy will stop sending the deprecated coverage status from June 5th, 2024. Learn more
Files with Coverage Reduction | New Missed Lines | % | ||
---|---|---|---|---|
io_preview.py | 6 | 94.38% | ||
create.py | 7 | 87.88% | ||
<!-- | Total: | 13 | --> |
Totals | |
---|---|
Change from base Build 8883544557: | 0.7% |
Covered Lines: | 3269 |
Relevant Lines: | 3698 |
So the issue is that I want IO to be well-defined at the class level, and I want to be able to dynamically create nodes, and I want it all to be pickle-able. These things are important for ideas like a "for-loop", where we want to be able to make new nodes on-the-fly for parsing different sizes of input, or for converting dataclasses into nodes (cf. #268).
What's here is still super rough -- it's too verbose and has too much unnecessary misdirection -- but the basic idea is to make some child of
StaticNode
that leverages different class attributes to construct it's IO pattern (and implements whatever functionality you want for the node), then on top of that to have a constructor function that dynamically makes subclasses of this node with different class attribute set.This directly accomplishes the first two goals, and plays nicely with pickle's
__reduce__
dunder, which lets you specify a custom reconstructor. (Although h5io still hard fails for custom reconstructors, so these nodes won't be storable in that paradigm.)The first case of these is simple transformer nodes, like converting a bunch of input to a list or vice-versa. From the outside this looks like the old to- and from-list nodes, which were constructed by defining code-as-string and then exec'ing it, but now it's much more cleanly done.
Example) a simple auto-encoder
Note how the entire workflow is pickleable! I want to extend the idea of meta-nodes to
Function
andMacro
instances (i.e. those not created via decorators, which tend to be directly importable anyhow -- Ok, let's do an asideWorks totally fine these days, but
Gives a pickling error,
PicklingError: Can't pickle <class '__main__.foo_function'>: it's not the same object as __main__.foo_function
/aside )
I'm playing around with
__new__
and anas_meta_node
decorator to try and get a cleaner abstraction, but it's not there yet.UPDATE:
I'm still not super satisfied with the user-facing syntax, which remains more verbose than I'd like, but I'm getting happier with the abstraction.
I now introduce two new features:
snippets.singleton.registered_factory
, which lets your class factories return the same object when they would return classes with the same name, andsnippets.constructed.Constructed
andsnippets.constructed.mix_and_construct_instance
which are a mix-in class and wrapper function, respectively, for making dynamically created classes pickleable (via__reduce__
, just like earlier comments).I am quite convinced that there's a way to integrate these two into something more succinct and powerful, but I think I'll pause here and move on with some practical stuff. They are already super useful for the classes converting input channels to a list and lists to output channels. The only thing that totally drives me crazy right now is using stuff as a decorator can get ugly, since then python can't find the underlying factory function to import.
Anyhow, here is a working example using the
Constructed
mixin:And using the wrapper for classes that don't inherit from
Constructed
to achieve the same result:UPDATE 2:
I tried using it and didn't like it, so I came back. I'm now super, duper happy. There is both a decorator interface
@classfactory
, and a constructorClassFactory
that decorate functions returning a tuple in direct analogy to the one consumed bybuiltins.type
. Resulting factories (classes) have object equivalence based on the factory function (generated class name) -- that means users are responsible for making sure their class names are non-degenerate, but IMO that is a totally fair requirement. Factories, classes, and resulting instances are all (un)pickleable, and factory-generated classes can themselves be re-used in downstream factory functions without trouble.I'll apply these changes to the
Transformer
stuff, but first I want to go write a little pedagogical blog post for the upcoming meeting.UPDATE 3:
classfactory
worked exactly as hoped for the transformers. I switched from__init_subclass__
over to just defining (sometimes un-defaulted)typing.ClassVar
attributes, since__init_subclass__
was just too much of a pain when one abstract class inherits from another.I.e. this throws a super annoying type error:
So I opted for this pattern instead: