4
\$\begingroup\$

I'm writing a program that needs to be compatible for both Python 2.X and Python 3.X. My problem is that, because of the differences between these two versions, I'm duplicating code.

try:
        ### Python 2.X part
        time.sleep(iterator.next())
        if len(arguments) >= len(inspect.getargspec(task).args):
            task(*arguments)
        else:
            print("ERROR: There is a mismatch between the number of"
                  " arguments provided in `arguments` and the ones"
                  " needed by the task function.")
except AttributeError:
        ### Python 3.X part
        time.sleep(next(iterator))
        if len(arguments) >= len(inspect.getfullargspec(task).args):
            task(*arguments)
        else:
            print("ERROR: There is a mismatch between the number of"
                  " arguments provided in `arguments` and the ones"
                  " needed by the task function.")

It can be seen that both versions only differ in the usage of next(), inspect.getargspec() and inspect.getfullargspec(), while the other logic remains the same. Is there any way to optimize the execution of the tasks, in tasks(*arguments), and the printing of the error messages, so that I don't have to repeat the same code? This is just sample working code, but I mean my question in a more broader and general case.

\$\endgroup\$
3
\$\begingroup\$

You can use next() function in both Python 2.6+ and 3.x. If you need to support Python < 2.6, you can have your own next() function. You can, for instance, check if referring to next throws a NameError and define it if it does:

try:
    next
except NameError:
    _sentinel = object()
    def next(it, default=_sentinel):
        try:
            return it.next()
        except StopIteration:
            if default is _sentinel:
                raise
            return default

Even though getargspec() got deprecated since 3.0, it still can be used and imported. In this case, you can import getfullargspec() and catch the ImportError:

try:
    from inspect import getfullargspec as get_args  # Python 3
except ImportError:
    from inspect import getargspec as get_args  # Python 2

Taking these both points into account, the code would look like:

time.sleep(next(iterator))
if len(arguments) >= len(get_args(task).args):
    task(*arguments)
else:
    print("ERROR: There is a mismatch between the number of"
          " arguments provided in `arguments` and the ones"
          " needed by the task function.")

As far as generally supporting multiple Python versions in a single codebase, six is a must use package.


Also note that instead of using inspect to pre-validate the number of arguments, you can follow the EAFP approach, call the function and handle a TypeError:

>>> f = lambda x, y: 'test'
>>> f(1,2,3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes exactly 2 arguments (3 given)
\$\endgroup\$
  • \$\begingroup\$ Thanks for the clear explanation. It was the first time I heard about the EAFP approach. \$\endgroup\$ – tulians Feb 15 '17 at 19:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.