0

I'm writing a command-line enhancer program. I chose to do this instead of using optparse, argparse, or the others available because: 1) I wanted the experience; and 2) I don't like them. ;)

A simple example:

@Script(
    live_daemon=Spec('test the installed daemon', FLAG, None, remove=True),
        verbose=Spec('more info on tests', FLAG),
        port=Spec('port for daemon to use for mini-tcp server', OPTION, type=int, remove=True),
        log=Spec('specific tests to log', MULTI, remove=True),
        tests=Spec('specific tests to run', OPTION),
        )
def main(live_daemon, verbose, log, port=8079, *tests):
    pass

As you can see from the port option above it is possible to specify the type of the argument that will be passed in to the function; the default type is str, but any callable is allowed.

One possibility not shown in the above example is a mapping:

@Script( random_vars=('for example', OPTION, type=(str, int)) )
def main(**random_vars):
    pass

Deep in the bowels of this utility program is a function that converts the incoming parameters to the default or requested types -- all it has to work with is a list to store the results, the function to use to do the conversion, and the data (which may be a singe item, or two items).

This is the sticky point: the default converter function should correctly handle any number of input items, and simply return them as-is -- rather than have a series of if/else blocks to select the correct default converter... in other words, an identity function.

The two lambdas I have now look like:

lamba x: x
lambda x, y: (x, y)

and what I would really like is a single function:

identity(x) --> x
identity(x, y) --> x, y
3
  • 1
    This might appear to be a duplicate of stackoverflow.com/q/8748036/208880, but in that question the OP accepted his own answer, which only gives the two wrong solutions. Commented Oct 16, 2014 at 0:38
  • ok. I've looked at the link. It appears to be the exact duplicate (as @Ethan Furman's (OP) answer demonstrates). Commented Oct 16, 2014 at 1:43
  • @J.F.Sebastian: rewrote question Commented Oct 16, 2014 at 2:31

1 Answer 1

0

No, Python does not have an identity function, and is unlikely to get one.

What would one look like? Easier, perhaps, to answer what it should do:

something = ...
something is identity(something)

in other words, you should get back exactly what you put in, and, ideally, you should be able to put anything in.

A tempting, but inadequate, solution:

wrong_id1 = lambda x: x

The problem with this version is that we can only pass single items in, so this works:

>>> wrong_id1('a thing')
'a thing'

but this does not:

>>> wrong_id1('a thing', 'and another thing')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes exactly 1 argument (2 given)

The next tempting solution might be:

wrong_id2 = lambda *x: x

and this does work for multiple inputs:

>>> (1, 2, 3) == wrong_id2(1, 2, 3)
True

but no so well for single inputs:

>>> 9 == wrong_id2(9)
False

The problem is that in order to accept multiple inputs, *args must be used, but it has the side-effect of transforming even single-item inputs into a tuple, and the naive wrong_id2 simply returns that tuple:

>>> wrong_id2(9)
(9,)

Now that we know what the problems are, we can write a correct identity function:

def identity(*args):
    if len(args) == 1:
        # only one item passed in, return that one item
        return args[0]
    # many items passed in, return them as a tuple
    return args

Some simple tests to show it works as advertised:

>>> 'a thing' == identity('a thing')
True

>>> (1, 2, 3) == identity(1, 2, 3)
True

>>> a_tuple = 7, 8, 9
>>> a_dict = {True: 'Python Rocks!', False: 'up is down'}
>>> none = None
>>> (a_tuple, a_dict, none) == identity(a_tuple, a_dict, none)
True
>>> a_tuple is identity(a_tuple)
True
>>> a_dict is identity(a_dict)
True
>>> none is identity(none)
True

DaoWen has made some interesting points against this implementation of identity, the chief one being consistency of return value -- that is, if even the single item case returns a tuple, then we can always treat it the same: call len() on it, put it in a for loop, etc, etc.

While that can be very handy behavior, that is not an identity function, for the simple reason that 1 != (1, ) -- therefore, identity(1) should return 1 and not (1, ).

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

13 Comments

The whole issue with lambda x: x being a unary function doesn't make sense to me. What's wrong with having a separate identity function for scalars vs tuples? It should always be obvious which is the correct choice for a given situation. However, I can't actually think of a concrete situation where I'd need a non-unary identity function.
your identity() function doesn't work with starmap(). Why do you support multiiterable map() variant but not starmap() variant? What is the logic here?
On another note, your final "correct" identity function still seems broken. You really just need separate tuple vs scalar identity functions. Here's a simple example: xs = [[1,2], [3], [4,5,6], [7], [8,9]]; ys = [ identity(*x) for x in xs ]. The resulting value for ys is [(1, 2), 3, (4, 5, 6), 7, (8, 9)]. I would have expected 1-tuples to be returned for 3 and 7. As a result, this expression gives a type error: zs = map(len, ys). Edit: I actually think that this is the same point that @J.F.Sebastian was making about starmap.
There are cases where you do not know ahead of time which you will need. In those cases you can either put in an if/else block to select the correct one, or just have a single function smart enough to handle both situations.
@DaoWen: Passing in *x is not the same as passing in x. Take out the * and you get exactly what you put in.
|

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.