-
Notifications
You must be signed in to change notification settings - Fork 265
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
Many improvements to curry
based on #147 #150 #155 #159
#170
Changes from 4 commits
c655b9c
b2f9c02
34f14ac
cc1a609
b44da40
027ea6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -141,50 +141,84 @@ def __init__(self, func, *args, **kwargs): | |
if not callable(func): | ||
raise TypeError("Input must be callable") | ||
|
||
self.func = func | ||
self.args = args | ||
self.keywords = kwargs if kwargs else None | ||
self.__doc__ = self.func.__doc__ | ||
# curry- or functools.partial-like object? Unpack and merge arguments | ||
if (hasattr(func, 'func') and hasattr(func, 'args') | ||
and hasattr(func, 'keywords')): | ||
_kwargs = {} | ||
if func.keywords: | ||
_kwargs.update(func.keywords) | ||
_kwargs.update(kwargs) | ||
kwargs = _kwargs | ||
args = func.args + args | ||
func = func.func | ||
|
||
if kwargs: | ||
self._partial = partial(func, *args, **kwargs) | ||
else: | ||
self._partial = partial(func, *args) | ||
|
||
try: | ||
self.func_name = self.func.func_name | ||
except AttributeError: | ||
pass | ||
self._hashvalue = hash((self.func, self.args, | ||
frozenset(self.keywords.items()) | ||
if self.keywords else None)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are your thoughts on having this in the constructor rather than having it inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is faster to have this in the constructor. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again I think that hashing curry objects isn't likely to be a bottleneck and so my preference here is to keep the code clean. Mostly I'm just anal about keeping There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough. You're the one who requested this feature, so I'll go with your preference :) |
||
except TypeError: | ||
self._hashvalue = None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might this cause problems? How do systems that call for hashes respond to a null value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one of the preferred way to remove hashability (for instance, if a new class inherits from a hashable class, but is no longer hashable). In other words, this behaves as expected. It is also included in the test suites. |
||
self.__doc__ = getattr(func, '__doc__', None) | ||
self.__name__ = getattr(func, '__name__', '<curry>') | ||
|
||
@property | ||
def func(self): | ||
return self._partial.func | ||
|
||
@property | ||
def args(self): | ||
return self._partial.args | ||
|
||
@property | ||
def keywords(self): | ||
return self._partial.keywords | ||
|
||
@property | ||
def func_name(self): | ||
return self.__name__ | ||
|
||
def __str__(self): | ||
return str(self.func) | ||
|
||
def __repr__(self): | ||
return repr(self.func) | ||
|
||
def __call__(self, *args, **_kwargs): | ||
args = self.args + args | ||
if _kwargs: | ||
kwargs = {} | ||
if self.keywords: | ||
kwargs.update(self.keywords) | ||
kwargs.update(_kwargs) | ||
elif self.keywords: | ||
kwargs = self.keywords | ||
else: | ||
kwargs = {} | ||
def __hash__(self): | ||
return self._hashvalue | ||
|
||
def __eq__(self, other): | ||
return (isinstance(other, curry) and self.func == other.func and | ||
self.args == other.args and self.keywords == other.keywords) | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this necessary? I thought that this happened by default. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is indeed necessary. Without it, |
||
|
||
def __call__(self, *args, **kwargs): | ||
try: | ||
return self.func(*args, **kwargs) | ||
return self._partial(*args, **kwargs) | ||
except TypeError: | ||
required_args = _num_required_args(self.func) | ||
|
||
# If there was a genuine TypeError | ||
if required_args is not None and len(args) >= required_args: | ||
required_args = _num_required_args(self.func) | ||
if (required_args is not None and | ||
len(args) + len(self.args) >= required_args): | ||
raise | ||
|
||
# If we only need one more argument | ||
if (required_args is not None and required_args - len(args) == 1): | ||
if kwargs: | ||
return partial(self.func, *args, **kwargs) | ||
else: | ||
return partial(self.func, *args) | ||
if args or kwargs: | ||
return curry(self._partial, *args, **kwargs) | ||
return self | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we just return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. Shall I change it? |
||
|
||
# pickle protocol because functools.partial objects can't be pickled | ||
def __getstate__(self): | ||
return self.func, self.args, self.keywords | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we pickle |
||
|
||
return curry(self.func, *args, **kwargs) | ||
def __setstate__(self, state): | ||
func, args, kwargs = state | ||
self.__init__(func, *args, **(kwargs or {})) | ||
|
||
|
||
@curry | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this check? Why not just always throw in the
kwargs
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a performance tweak.
partial
is faster whenkeywords
isNone
instead of{}
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that I don't care much about performance tweaks during function creation as I assume that these will happen only a few times. In these cases I think that code cleanliness is more important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, but this performance tweak isn't for initialization, it is for usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand. This is only going to happen whenever I add new args onto the
curry
, right? I guess that in my use of curried functions this happens only a few times. Can you supply a motivating example?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The performance increase is when the curried function gets called, not upon creating the curried object. Maybe this will clarify:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, now I understand. Alright, I guess that this makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's subtle. One would expect
partial
to behave the same forf1
andf2
from above, but it doesn't.