0

This has probably been asked before, but I don't know how to look up the answer, because I'm not sure what's a good way to phrase the question.

The topic is function arguments that can semantically be expressed in many different ways. For example, to give a file to a function, you could either give the file directly, or you could give a string, which is the path to the file. To specify a number, you might allow an integer as an argument, or maybe you might allow a string (the numeral), or you might even allow a string such as "one". Another example might be a function that takes a list (of numbers, say), but as a convenience, it will convert a number into a list containing one element: that number.

Is there a more or less standard approach in Python to allowing this sort of flexibility? It certainly complicates the code for a program if you're not certain what types the arguments are, so my guess would be to try factor out the convenience functions into just one place, instead of scattered everywhere, but I don't really know how best to do that kind of factoring.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Daryl McCullough
  • 303
  • 5
  • 20
  • 1
    I'm guessing that there will be a number of responses that say: Don't do that! That might be good advice, but there is still the question of when a conversion between different forms of semantically equivalent objects needs to be done, where should it be done? In the calling function? That might lead to a lot of duplicate code. – Daryl McCullough Apr 08 '13 at 15:30
  • The specific example that came up (not my code, but code written by someone I work with) is files. A function that takes files as arguments can either take a string as an argument (interpreted as a path), or it can take an actual file (the result of the open() function). Another example that came up in the same code is a function that can either take a list of messages, or a single message. The latter case is interpreted as being the same as a list of length 1. I gave these examples already, so maybe they are still too abstract? What would make them more concrete for you? – Daryl McCullough Apr 08 '13 at 15:42
  • In your second example you could use `*args` to avoid the checks altogether(even though there are drawbacks). – Bakuriu Apr 08 '13 at 15:44

4 Answers4

0
  1. Don't do that! ;)

But if you still want to, I'd suggest that you have an intermediate class or function handling this for you:

Pseudocode:

def printTheNumber(num):
    print num


def intermediatePrintTheNumber(input):

    num_int_dict = {'one':1, "two":2 ....
    if input.isstring():
        printTheNumber(num_int_dict[input])
    elif input.isint():
        printTheNumber(input)
    else:
        print "Sorry Dave, I don't understand you"

If this is pythonic I don't know, but that's how I'd solve it if I had to, of course with some more checking of the input to deem it valid.

When it comes to your comment you mention semantic similarity i.e "one" and 1 might mean the same thing.

Where should this kind of conversion be made you ask.

Well that depends on the design of your system, but I can tell you that it should not be done in the same function that I call printTheNumber for one very simple reason, and that is that that would give that function way to much responsibility.

Depending on the complexity of the input it could be the integer 1 or the string "1" or, in the worse case, "one" or maybe even worse "uno"|"one"|"yxi"|"ett" .. and so on. This should be handled by a function that has only that responsibility maybe with a database handling the mapping.

I would split it up so that I have one function handling the the strings "one", "two" ... etc, and one handling integers and have a third function that checks the input to see if it can be converted to an integer or not.

As I see it, there is a warning for a fundamental flaw in the design if you have to take measures for this kind of complexity, but you seem to be aware of that so I won't go on about it.

Daniel Figueroa
  • 10,348
  • 5
  • 44
  • 66
0

A good way to factor out code common to several functions is through decorators. For example,

from functools import wraps

def takes_list(func):
    @wraps(func)
    def wrapper(arg):
        if not isinstance(arg, list):
            arg = [arg]
        return func(arg)
    return wrapper

@takes_list
def my_func(x):
    "Does something with list x."

I should note that for cases such as files, you don't want to get in the way of Python's duck typing: doing a check isinstance(arg, file) has the problem that it won't allow file-like things such as io.StringIO. Instead, check against str (or basestring) or even let open do the checking for you, with try-except.

However, it's generally better practice just to let the caller pass what they like into the function, and fail if it isn't valid.

Abe Karplus
  • 8,350
  • 2
  • 27
  • 24
  • Regarding files you took the wrong "direction". You generally want to do the `str -> file` conversion, not the opposite, hence you should check if the object is `str` and not `file`. Also, `open` **does** check the type of the argument, hence doing `isinstance(file, str): file = open(file)` doesn't remove any flexibility to the method. – Bakuriu Apr 08 '13 at 15:48
  • @Bakuriu Good point. I've edited my answer to include checking for `str` instead of `file`, and also mentioned an EAFP way of using `open`. – Abe Karplus Apr 08 '13 at 16:11
0

No, there is not more or less a "standard" approach to this.

Mark Hildreth
  • 42,023
  • 11
  • 120
  • 109
-1
  1. Since you can not add methods at runtime to builtin classes such as int or str you would make a switch case statement like structure as mentioned by Daniel Figueroa.

  2. Another way would be to just convert:

    def func(i):
        if not isinstance(i, int):
            i = int(i) # objects can overwrite __int__ if needed.
    
  3. If you have own classes that you can add methods to, you may use double dispatch to do the same thing for you. Smalltalk uses this for the Integer-Float-... conversion.

  4. Another way would be to use subject oriented programming for which I have not found an implementation yet but I tried: https://gist.github.com/niccokunzmann/4971938

User
  • 14,131
  • 2
  • 40
  • 59