Skip to content
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

Support partial'd target in builds #199

Merged
merged 9 commits into from
Jan 13, 2022
Merged

Support partial'd target in builds #199

merged 9 commits into from
Jan 13, 2022

Conversation

rsokl
Copy link
Contributor

@rsokl rsokl commented Jan 13, 2022

To-Do

  • Update type annotations so that instantiate(builds(partial(A))) resolves to A
    • It turns out that this just works! Added some pyright tests.
  • Document in changelog and in reference docs for builds

This PR

Closes #197

builds now can accept a "partial'd" object as a target 🚀 🚀 🚀

Consider the following:

from hydra_zen import builds, instantiate
from functools import partial
from inspect import signature

def f(x: int, y: str): 
    return (x, y)

Before:

>>> partial_f = partial(f, x=1)
>>> builds(partial_f, y="a")
---------------------------------------
AttributeError: no attribute __name__

After:

>>> partial_f = partial(f, x=1)
>>> Conf = builds(partial_f, y="a")
>>> instantiate(Conf)
(1, 'a')

>>> signature(builds(partial_f))
<Signature (x: int = 1) -> None>

>>> signature(builds(partial_f, y="a"))
<Signature (x: int = 1, y: str = 'a') -> None>

>>> signature(builds(partial_f, populate_full_signature=True))
<Signature (y: str, x: int = 1) -> None>

Implementation Details

builds "unpacks" the partial'd target and treats the .args and .keywords of the partial'd target as if they were specified as configured values.

>>> from hydra_zen import to_yaml
>>> print(to_yaml(builds(partial_f, y="a")))
_target_: __main__.f
x: 1
'y': a

However, these are always treated as "inner" arguments, so that if a keyword argument is also specified by the user, it takes priority.

E.g.

>>> def g(x): return x
>>> Conf = builds(partial(g, x=1), x=-22)
>>> instantiate(Conf)
-22

this behavior is chosen specifically to have parity with partial:

>>> partial(g, x=1)(x=-22)
-22

This "unpacking" behavior is really nice. We can still validate partial'd objects:

>>> builds(partial(f, x=1), 1)
TypeError: Building: f ..
Multiple values for argument x were specified for __main__.f via `builds`

and our other bells and whistles work too:

>>> instantiate(builds(partial(g, x=1+2j)))  # complex values in partial'd objects are OK
(1+2j)

One design decision here is that we do not "hide" any of the partial'd fields from the user. E.g. in

def f(x, y): 
    return (x, y)

partial_f = partial(f, x=1)
builds(partial_f, populate_full_signature=True)  # signature: (y, x=1)

for builds(partial_f, populate_full_signature=True) we could exclude x from the __init__/signature of the config. This would add some complications to the implementation, but the more pressing reason for why we didn't do this is that it would disrupt our parity with functools.partial. As demonstrated above, one can name a field even after it has been named within the partial:

>>> partial(f, x=1)(x=88, y=0)  # x is specified twice
(88, 0)

@rsokl rsokl added the enhancement New feature or request label Jan 13, 2022
@rsokl rsokl added this to the hydra-zen 0.5.0 milestone Jan 13, 2022
@rsokl rsokl requested a review from jgbos January 13, 2022 05:32
@rsokl rsokl merged commit 5d30544 into main Jan 13, 2022
@rsokl rsokl deleted the support-partiald-target branch January 13, 2022 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide support for functools.partial within config-creation funcs
2 participants