4

Very often I process single elements of tuples like this:

size, duration, name = some_external_function()
size = int(size)
duration = float(duration)
name = name.strip().lower()

If some_external_function would return some equally typed tuple I could use map in order to have a (more functional) closed expression:

size, duration, name = map(magic, some_external_function())

Is there something like an element wise map? Something I could run like this:

size, duration, name = map2((int, float, strip), some_external_function())

Update: I know I can use comprehension together with zip, e.g.

size, duration, name = [f(v) for f, v in zip(
   (int, float, str.strip), some_external_function())]

-- I'm looking for a 'pythonic' (best: built-in) solution!

To the Python developers:

What about

(size)int, (duration)float, (name)str.strip = some_external_function()

? If I see this in any upcoming Python version, I'll send you a beer :)

6
  • I would think a custom class that models your data would make sense here, instead of treating values individually. That class could also care about casting arguments to their correct types, which I might do as an alternative constructor like foo = SomeExternalData.from_string(*some_external_function()); print(foo.size) Commented Apr 6, 2020 at 10:22
  • 1
    I'd say, the whole point of map (as a concept in functional programming) is to apply one function to each element of the list. So, your map2 won't really be a "true" map then. Commented Apr 6, 2020 at 10:23
  • you're right - my point is not to use map but to have a readable, short closed expression in the end without states, intermediate variables or re-assigning variables. map is just a way to show what I mean. Commented Apr 6, 2020 at 10:26
  • long time ago I've implemented this kind of utility in lz package, if you don't want extra dependency I can write a recipe as an answer Commented Apr 6, 2020 at 10:40
  • 1
    Does this answer your question? Zip and apply a list of functions over a list of values in Python Commented Apr 7, 2020 at 8:48

6 Answers 6

6

Quite simply: use a function and args unpacking...

def transform(size, duration, name):
    return int(size), float(duration), name.strip().lower()

# if you don't know what the `*` does then follow the link above...    
size, name, duration = transform(*some_external_function())

Dead simple, perfectly readable and testable.

Sign up to request clarification or add additional context in comments.

2 Comments

Your answer - in my eyes - shows how it should be done if you plan to re-use transform. If I could select two answers, yours would be one of them. In my scenario I have several transformations to do - each different from the other, so a one-liner like size, name, duration = transform((int, float, str.strip), some_external_function() is more what I need now.
It actually doesn't have much to do with reuse - that's typically what I do even for one-shot scripts. Trying to be generic for the sake of being generic is ibn my experience mostly a waste of time. Now it of course, once again, depends on the context and concrete use case, so only you can tell which approach fits your current needs best ;-)
4

Map does not really apply here. It comes in handy when you want to apply a simple function over all elements of a list, such as map(float, list_ints).

There isn't one explicit built-in function to do this. However, a way to simplify your approach and avoid n separate calls to the functions to be applied, could be to define an iterable containing the functions, and apply them to the returned non-unpacked tuple from the function on a generator comprehension and then unpack them:

funcs = int, float, lambda x: x.strip().lower()
t = 1., 2, 'Some String  ' # example returned tuple

size, duration, name = (f(i) for f,i in zip(funcs, t))

Or perhaps a little cleaner:

def transform(t, funcs):
    return (f(i) for f,i in zip(funcs, t))

size, duration, name = transform(t, funcs)

size
# 1
duration
# 2.0
name
# 'some string'

1 Comment

(size)int, (duration)float, (name)str.strip = some_external_function() (i.e. 'reciprocal' functions) is what should exist in my eyes :). Since it doesn't, your answer seems to fit my intentions
3
class SomeExternalData:
    def __init__(self, size: int, duration: float, name: str):
        self.size = size
        self.duration = duration
        self.name = name.strip().lower()

    @classmethod
    def from_strings(cls, size, duration, name):
        return cls(int(size), float(duration), name)


data = SomeExternalData.from_strings(*some_external_function())

It's far from a one-liner, but it's the most declarative, readable, reusable and maintainable approach to this problem IMO. Model your data explicitly instead of treating individual values ad hoc.

3 Comments

Well... depends on your use case, really. I would certainly do something similar if those were really domain model stuff, but definitly not for a simple parsing where those data will be actually used to build or update or query existing domain model objects ;-)
"Some external function" which returns strings sounds like an external API of some sort, and IMO it makes sense to model that as its own thing. It makes it easier to write adapter code that turns that external format into some internal format. If you don't model what format your external data source uses and just always treat it ad hoc, that makes long term maintenance that much harder, especially if you use it a lot.
might just be reading a csv file to update a database for what we know - as I said, it really depends on the context, which we don't have. Note that I upvoted your answer FWIW - I'm not disagreeing on the advice, just mentionning that depending on your real use case it can be either the RightThing(tm) or just plain overkill.
2

AFAIK there is no built-in solution so we can write generic function ourselves and reuse it afterwards

def map2(functions, arguments):  # or some other name
    return (function(argument) for function, argument in zip(functions, arguments))  # we can also return `tuple` here for example

The possible problem can be that number of arguments can be less than number of functions or vice versa, but in your case it shouldn't be a problem. After that

size, duration, name = map2((int, float, str.strip), some_external_function())

We can go further with functools.partial and give a name to our "transformer" like

from functools import partial
...
transform = partial(map2, (int, float, str.strip))

and reuse it in other places as well.

Comments

0

Based on Bruno's transform, which I think is the best answer to the problem, I wanted to see if I could make a generic transform function that did not need a hardcoded set of formatters, but could take any number of elements, given a matching number of formatters.

(This is really overkill, unless you need a large number of such magic mappers or if you need to generate them dynamically.)

Here I am using Python 3.6's guaranteed dictionary order to "unpack" the formatters in their declared order and separate them from inputs variadic.

def transform(*inputs, **tranformer):
    return [f(val) for val, f in zip(inputs, tranformer.values())]

size, duration, name = transform(*some_external_function(), f1=int, f2=float, f3=str.lower)

And to make the process even more generic and allow predefined transform functions you can use operator.partial.

from functools import partial

def prep(f_tranformer, *format_funcs):
    formatters = {"f%d"%ix : func for ix, func in enumerate(format_funcs)} 
    return partial(transform, **formatters)


transform2 = prep(transform, int, float, str.lower)

which you can use as:

size, duration, name = transform2(*some_external_function())

Comments

0

I'll second bruno's answer as being my preferred choice. I guess it will depend on how often you are calling this function will determine how much value it is in refactoring such a hindrance. If you were going to be calling that external function multiple times, you could also consider decorating it:

from functools import wraps

def type_wrangler(func):
    def wrangler():
        n,s,d = func()
        return str(n), int(s), float(d)
    return wrangler


def external_func():
    return 'a_name', '10', '5.6'

f = type_wrangler(external_func)

print(f())

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.