2

As a system administrator I find myself many times writing scripts that call command via subprocess. Sometimes, I don't want the commands to actually execute, and I want just to see what would be executed. Hence, my codes were full in lines like this:

alaroffcmd = 'someBinary -h %s' %someHostName
...
if options.tstmd:       
   print alaroffcmd
else:
   chctxt = sp.Popen(alamoffcmd,shell=True, stdout=sp.PIPE) 
   ...

I was thinking that a 'testmode' would be very usefull.

As an example of usage:

lsp=nPopen('ls -l',shell=True, stdout=sp.PIPE, testmode=True)

Will just print the command to be issued. This seems redundant maybe, but in real life, I sometimes call subprocess with some very complex command, which are decided based on conditions that are determined in the script (above there is an example with someHostName)

I used this as an example how to extend a function by overriding it's init method,. Here is how I extended subprocess.Popen, to fit my needs:

import subprocess as sp

class nPopen(sp.Popen):
    def __init__(self, args, bufsize=0, executable=None,
                 stdin=None, stdout=None, stderr=None,
                 preexec_fn=None, close_fds=False, shell=False,
                 cwd=None, env=None, universal_newlines=False,
                 startupinfo=None, creationflags=0,testmode=False):

        if testmode: 
            print args
            return None

     p = sp.Popen.__init__(self,args, bufsize, executable,
                 stdin, stdout, stderr,
                 preexec_fn, close_fds, shell,
                 cwd, env, universal_newlines,
                 startupinfo, creationflags)

        return p

This works as I expect it, but since I have never extended a class by overriding its __init__ method, I was wondering about the correctness of this, or in other words: Is there a more Pythonic way to do this? Should I use super for better Python3 compatibility ?

Community
  • 1
  • 1
oz123
  • 27,559
  • 27
  • 125
  • 187
  • BTW, `__init__()` functions don't return anything. – martineau Nov 14 '12 at 18:34
  • @martineau, as a convention ? or as a fact? – oz123 Nov 14 '12 at 18:36
  • 1
    A fact. Actually all functions return something, `None` by default if they don't explicitly return something else, but whatever it is, it's ignored in the case of a class's `__init__()` function. You know, you could just change the value of `Popen` in the module after you `import subrocess as sp` to be your own function. i.e. `sp.Popen = my_function`. – martineau Nov 14 '12 at 18:39
  • @martineau, thanks for making that clear. It works also without the statement `return p` in the end. I appreciate your comment. – oz123 Nov 14 '12 at 18:46
  • Yes, all an `__init__()` usually does is initialize the already created instance's attributes (and possibly other miscellaneous things). Class instances are normally created by the class's `__new__` function inherited from `object` in modern Python, and as such isn't commonly overridden. – martineau Nov 14 '12 at 18:55

1 Answers1

4

I wouldn't use a subclass at all, since it sounds like you don't want to change the functionality of the Popen object. It sounds like you just want to change the functionality of its constructor.

I would create a dummy Popen class that just prints out its arguments. (This is written using Python 3.x style, but it's easy enough to translate to Python 2.x.)

class DummyPopen(object):
    def __init__(self, *args, **kw):
        print('Popen({})'.format(
            ', '.join([repr(arg) for arg in args] +
                      ['{}={!r}'.format(*pair) for pair in kw.items()])))
        self.returncode = 1

    def wait(self):
        return 1

    # et cetera

You can then trigger use of the dummy class however you want.

def Popen(*args, testmode=False, **kw):
    if testmode:
        return DummyPopen(*args, **kw)
    return subprocess.Popen(*args, **kw)

Or even:

if testmode:
    Popen = DummyPopen
else:
    Popen = subprocess.Popen

This takes advantage of the "duck typing" conventions of Python -- any object which has the same interface as a Popen object is functionally indistinguishable from a Popen object, as long as you don't abuse isinstance() too much.

Note that both of these methods allow you to import Popen from the module.

from mymodule import Popen
Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • Thanks for showing me such a nice way not to abuse inheritance. Your second way with `def Popen` is more clear to me (I am not really into Python3, yet..) . It has the advantage I can also import it from other scripts, compared to the last option ... – oz123 Nov 14 '12 at 18:16
  • @Oz123: The last option can also be imported from other scripts. – Dietrich Epp Nov 14 '12 at 22:03
  • How can you import a collection of statements not grouped in a function or a class? – oz123 Nov 15 '12 at 09:19
  • @Oz123: Classes and functions are not special, they are just global variables and you can import any global variable from a module. For example, if you write `x = 5` in module `mymodule`, then you can refer to it as `mymodule.x`. – Dietrich Epp Nov 15 '12 at 18:35
  • I failed to ask the wrong quesiton. When you said you can import the 3rd option, i thought you mean the `if testmode: Popen = DummyPopen else: Popen = subprocess.Popen` structure ... I know you can import a variable or a function ... – oz123 Nov 15 '12 at 20:54
  • @Oz123: Yes, you can import `Popen` from the `if testmode: ...` statement. If you failed to ask the wrong question, what is the right question? What are you trying to do with the last option? – Dietrich Epp Nov 15 '12 at 22:58
  • one more thing, I think I understand your comment. When I import the module, that hold the new Popen, the statement `testmode` is evaluated, so it decides which Popen to import? the only problem with this is that `testmode` from the importing module is not available inside where Popen is defined... after giving some thought, and correcting my typos, here is the solution I came up with: http://paste.debian.net/209684/ this is quite a journey ... thanks for making me scratch my brain. – oz123 Nov 15 '12 at 23:51
  • Hmmm... let me phrase it this way. When you import a module, all of the statements at the top level of that module get evaluated (but only once, even if you import multiple times). `Popen` is just a global variable in a module. It doesn't matter if `Popen` is created using a `class` statement, or a `def` statement, or something else. When you import something, all you are doing is accessing the global variables in another module. Hope this helps. – Dietrich Epp Nov 16 '12 at 00:03