Skip to content

Commit 185052a

Browse files
committed
change the unpacking behavior of compose and pipe to unpack only if isinstance(return_value, (list, tuple))
1 parent b7a36fe commit 185052a

File tree

2 files changed

+22
-37
lines changed

2 files changed

+22
-37
lines changed

unpythonic/fun.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -294,16 +294,15 @@ def compose1(fs):
294294
return compose1
295295

296296
def _make_compose(direction): # "left", "right"
297-
def unpack_ctx(*args): pass # just a context where we can use * to unpack
298297
def compose_two(f, g):
299298
def composed(*args):
300299
a = g(*args)
301-
try:
302-
unpack_ctx(*a) # duck test
303-
except TypeError:
304-
return f(a)
305-
else:
300+
# we could duck-test but this is more predictable for the user
301+
# (consider chaining functions that manipulate a generator).
302+
if isinstance(a, (list, tuple)):
306303
return f(*a)
304+
else:
305+
return f(a)
307306
return composed
308307
compose_two = (flip if direction == "right" else identity)(compose_two)
309308
def compose(fs):
@@ -352,16 +351,12 @@ def composer(*fs):
352351
353352
This mirrors the standard mathematical convention (f ∘ g)(x) ≡ f(g(x)).
354353
355-
The output from each function is unpacked to the argument list of
356-
the next one. If the duck test fails, the output is assumed to be
357-
a single value, and is fed in to the next function as-is.
358-
359-
**CAUTION**:
360-
361-
This implicit unpacking will unpack also generators!
354+
At each step, if the output from a function is a list or a tuple,
355+
it is unpacked to the argument list of the next function. Otherwise,
356+
we assume the output is intended to be fed to the next function as-is.
362357
363-
If you have a chain of functions that manipulate a single generator,
364-
use ``composer1`` instead.
358+
Especially, generators, namedtuples and any custom classes will **not** be
359+
unpacked, regardless of whether or not they support the iterator protocol.
365360
"""
366361
return composeri(fs)
367362

unpythonic/seq.py

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@ def pipe(values0, *bodys):
247247
The only restriction is that each function must take as many positional
248248
arguments as the previous one returns.
249249
250-
The output from each function is unpacked to the argument list of
251-
the next one. If the duck test fails, the output is assumed to be
252-
a single value, and is fed in to the next function as-is.
250+
At each step, if the output from a function is a list or a tuple,
251+
it is unpacked to the argument list of the next function. Otherwise,
252+
we assume the output is intended to be fed to the next function as-is.
253253
254254
If you only need a one-in-one-out chain, ``pipe1`` is faster.
255255
@@ -271,15 +271,12 @@ def pipe(values0, *bodys):
271271
lambda x, y, s: (x + y, s))
272272
assert (a, b) == (13, "got foo")
273273
"""
274-
def unpack_ctx(*args): pass # just a context where we can use * to unpack
275274
xs = values0
276275
for update in bodys:
277-
try:
278-
unpack_ctx(*xs) # duck test
279-
except TypeError:
280-
xs = update(xs) # single x only
276+
if isinstance(xs, (list, tuple)):
277+
xs = update(*xs)
281278
else:
282-
xs = update(*xs) # multiple xs
279+
xs = update(xs)
283280
return xs
284281

285282
class piped:
@@ -300,14 +297,10 @@ def __or__(self, f):
300297
return self._xs
301298
else:
302299
cls = self.__class__
303-
def unpack_ctx(*args): pass # just a context where we can use * to unpack
304-
xs = self._xs
305-
try:
306-
unpack_ctx(*xs) # duck test
307-
except TypeError:
308-
return cls(f(self._xs)) # single x only
300+
if isinstance(self._xs, (list, tuple)):
301+
return cls(*f(*self._xs))
309302
else:
310-
return cls(*f(*self._xs)) # multiple xs
303+
return cls(f(self._xs))
311304
def __repr__(self):
312305
return "<piped at 0x{:x}; values {}>".format(id(self), self._xs)
313306

@@ -333,15 +326,12 @@ def __init__(self, *xs, _funcs=None):
333326
def __or__(self, f):
334327
"""Pipe the values into f; but just plan to do so, don't perform it yet."""
335328
if f is get: # compute now
336-
def unpack_ctx(*args): pass # just a context where we can use * to unpack
337329
vs = self._xs
338330
for g in self._funcs:
339-
try:
340-
unpack_ctx(*vs) # duck test
341-
except TypeError:
342-
vs = g(vs) # single v only
331+
if isinstance(vs, (list, tuple)):
332+
vs = g(*vs)
343333
else:
344-
vs = g(*vs) # multiple vs
334+
vs = g(vs)
345335
return vs
346336
else:
347337
# just pass on the references to the original xs.

0 commit comments

Comments
 (0)