views:

128

answers:

2

This question came from looking at this question on Stackoverflow.

def fringe8((px, py), (x1, y1, x2, y2)):

Personally, it's been one of my pet peeves to see a function that takes two arguments with fixed-number iterables (like a tuple) or two or more dictionaries (Like in the Shotgun API). It's just hard to use, because of all the verbosity and double-bracketed enclosures.

Wouldn't this be better:

>>> class Point(object):
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...     
>>> class Rect(object):
...     def __init__(self, x1, y1, x2, y2):
...         self.x1 = x1
...         self.y1 = y1
...         self.x2 = x2
...         self.y2 = y2
...     
>>> def fringe8(point, rect):
...     # ...
...
>>>
>>> point = Point(2, 2)
>>> rect = Rect(1, 1, 3, 3)
>>>
>>> fringe8(point, rect)

Is there a situation where taking two or more iterable arguments is justified? Obviously the standard itertools Python library needs that, but I can't see it being pretty in maintainable, flexible code design.

+7  A: 

The def syntax with unpacking, like

def fringe8((px, py), (x1, y1, x2, y2)):

is gone in Python 3 -- which means Guido considered it a design error, or at least an unwarranted complication. Named tuples might be even better than special-purpose classes for the clarifying purpose you suggest.

However, having functions that take multiple iterable arguments is just perfectly fine -- itertools obviously cannot encapsulate every multi-iterable manipulation your applications might need, of course! Thinking of an iterable as "a stream" (possibly an unbounded one), there are vast number of ways in which you might want to "merge" multiple streams into one, for example (e.g. think of streams that are known to be sorted and you might want to build the intersection of, or the union, with or without removal of duplicates, etc, etc).

Why ever should one contort one's API's design to obey a perfectly arbitrary injunction about having no more than one iterable per function signature?!

Alex Martelli
Ah good! So when Python3 is widely adapted, it won't be an issue for me anymore. That's very, very good.
Xavier Ho
@Xavier, right: "self-unpacked" tuple args, never a very popular style anyway, are gone in Python 3. But the other issues, if anything, get reinforced in Py3, with its iterable "views"!-)
Alex Martelli
@Alex, iterable "views"? Could you hint a little more?
Xavier Ho
self-unpacked tuple args are gone in Py3, but you can still write the function as `def fringe(pt, rect)` and call it exactly the same way.
Ned Batchelder
@Xavier, for example `somedict.keys()` in Python 3 returns, not a _list_ of the dict's keys, but a "view" within the dict showing the keys only (it's a view which you can loop on, i.e., an iterable view).
Alex Martelli
@Ned, sure, and `def fringe8(point, rect):` is exactly how the OP preferred to declare his function -- whether the args are built at the call site itself, or earlier and bound to names, is a separate style point, as is the args' type (tuples, class instances, or an "intermediate" approach like named tuples). But at least the syntax doesn't _encourage_ you to get tuple args and split them on the spot;-).
Alex Martelli
@Ned: Yes, I'm aware of that. `:]`. || @Alex: Oh, I see what you mean now. I always thought it returns a generator in Py3. Are we talking about the same thing?
Xavier Ho
@Xavier, yes, we're taking about the same thing, and what it **does** return is a view (of type `dict_keys` in the specific example). A generator has e.g. `send` and `throw` methods, a `dict_keys` has absolutely nothing like that -- very different kettles of fish, not sure how you could confuse them!
Alex Martelli
+1  A: 

I agree with you that people tend to over-use lists and dictionaries as data structures just because they can. They see it as a simple way to group like data that doesn't require any work to define a class or deal with constructors. However, when you start to do more tasks with this data, you find that it is hard to remenber which piece of data was 2nd on the list and which was 5th. Making a class allows things to be more clearly defined and lets you compartmentalize functions that work directly on your data structure.

While you are thinking about it as having multiple iterables, I don't see that as the problem here. Some functions really do require multiple lists (see the built-in zip). But I think the example you gave is a case where the tuples are being used because it seems easier, but may not be as clear.

unholysampler