41

I am running on a AIX 6.1 and using Python 2.7. Want to execute following line but getting an error.

subprocess.run(["ls", "-l"])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'run'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
nisarga lolage
  • 531
  • 1
  • 5
  • 14

2 Answers2

58

The subprocess.run() function only exists in Python 3.5 and newer.

It is easy enough to backport however:

def run(*popenargs, **kwargs):
    input = kwargs.pop("input", None)
    check = kwargs.pop("handle", False)

    if input is not None:
        if 'stdin' in kwargs:
            raise ValueError('stdin and input arguments may not both be used.')
        kwargs['stdin'] = subprocess.PIPE

    process = subprocess.Popen(*popenargs, **kwargs)
    try:
        stdout, stderr = process.communicate(input)
    except:
        process.kill()
        process.wait()
        raise
    retcode = process.poll()
    if check and retcode:
        raise subprocess.CalledProcessError(
            retcode, process.args, output=stdout, stderr=stderr)
    return retcode, stdout, stderr

There is no support for timeouts, and no custom class for completed process info, so I'm only returning the retcode, stdout and stderr information. Otherwise it does the same thing as the original.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    It didn't like input and check being in between popenargs and kwargs. – jjcf89 Oct 29 '19 at 14:02
  • 1
    @jjcf89: I'm not sure what to do with that information. Did you have want to see that handled differently? It's just a runtime check on the arguments passed in, and the [version in the standard library](https://github.com/python/cpython/blob/1d2862a323ae1387e306942253b1e487018e2b7f/Lib/subprocess.py#L477-L480) does *exactly the same thing*. – Martijn Pieters Oct 29 '19 at 17:38
  • @MartijnPieters I think he is saying that it won't run and throws an error when using your code as it is incompatible (in its current form) with Python 2.7. It throws an `invalid syntax` error and points to the `input=None` line. – Frak Nov 22 '19 at 18:37
  • @frakman1 yes, because in Python 2 you can’t just `*args` and named keyword arguments. Drop the named keyword arguments and look for them in `kwargs` instead, using `kwargs.pop()`. – Martijn Pieters Nov 22 '19 at 22:55
  • @frakman1 and I see this question is tagged with [tag:python2.7] so I’ve made that change. – Martijn Pieters Nov 22 '19 at 23:00
  • @MartijnPieters Thank you. I knew there needed to be a change but I just didn't know what it had to be as it is not obvious to a novice. Thank you for updating the answer to a more suitable form. – Frak Nov 23 '19 at 17:03
  • @frakman1: let me give you the obligatory warning too, though: Python 2 is reaching end-of-life in [just over a month](https://pythonclock.org/); if you have any choice in the matter I suggest you move to Python 3.x asap. :-) – Martijn Pieters Nov 23 '19 at 17:04
1

The accepted answer is non-working code and also doesn't have the same returns as the original (Python 3) function. It is similar enough, however, that it is not CC BY 4.0 Martijn Pieters because it is copied from Python and if any license applied to the trivial code (I'm against licensing of trivial code entirely since it hinders innovation and proving ownership or originality is difficult, so stackoverflow is likely violating various licenses including the GPL by adding additional restrictions by re-licensing things people paste and tacitly claim as their own without citing sources), that would be at the GitHub link below. It isn't "backporting" if the code isn't usable in the same way. You will just have "AttributeError: 'tuple' object has no attribute 'returncode'" if you try to use it the Python 3 way without changing your code (client code using the function). You may change your code but then it won't be compatible with Python 3.

Either way, the code in the accepted answer itself won't even run due to:

  1. "TypeError: init() got an unexpected keyword argument 'stderr'" (since stderr isn't an argument for "CalledProcessError" in either 2 or 3)

  2. "AttributeError: 'Popen' object has no attribute 'args'" (args is only available in Python 3)

Therefore, consider changing the accepted answer.

To be more Pythonic by taking advantage of duck typing and a polyfill (your client code can stay the same and use a different definition shown below for the run method and the returned object's class), here is an implementation that is compatible with Python 3:

import subprocess
import sys
if sys.version_info.major >= 3:
    from subprocess import CompletedProcess
else:
    # Add a polyfill to Python 2
    class CompletedProcess:

        def __init__(self, args, returncode, stdout=None, stderr=None):
            self.args = args
            self.returncode = returncode
            self.stdout = stdout
            self.stderr = stderr

        def check_returncode(self):
            if self.returncode != 0:
                err = subprocess.CalledProcessError(self.returncode, self.args, output=self.stdout)
                raise err
            return self.returncode

    def sp_run(*popenargs, **kwargs):
        input = kwargs.pop("input", None)
        check = kwargs.pop("handle", False)
        if input is not None:
            if 'stdin' in kwargs:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = subprocess.PIPE
        process = subprocess.Popen(*popenargs, **kwargs)
        try:
            outs, errs = process.communicate(input)
        except:
            process.kill()
            process.wait()
            raise
        returncode = process.poll()
        if check and returncode:
            raise subprocess.CalledProcessError(returncode, popenargs, output=outs)
        return CompletedProcess(popenargs, returncode, stdout=outs, stderr=errs)

    subprocess.run = sp_run
    # ^ This polyfill allows it work on Python 2 or 3 the same way

The most up-to-date and tested version is at a module I made: https://github.com/Hierosoft/hierosoft/blob/main/hierosoft/__init__.py. This code has been tested using test cases that work in Python 2 and 3 (my "nopackage" script here for example: https://github.com/Poikilos/nopackage which requires the module).

NOTE: The class doesn't have the same repr string and may have other minor differences (You can use the real code from Python 3 itself at the following URL according to its license--see class CalledProcess in: https://github.com/python/cpython/blob/master/Lib/subprocess.py --that license also applies to my code if any, but I release it as CC0 since it is what I consider trivial--see explanation in parenthesis above).

#IRejectTheInvalidAutomaticLicenseForMyPost

Poikilos
  • 1,050
  • 8
  • 11