2

I'm trying to build a function that would allow to repeat this (it prevents the console from opening in windows):

 if platform.system() == 'Windows':
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        subprocess.call(['myprogram.exe', '-P', arg], 
                       stdout=someFile, startupinfo=startupinfo)
    else:
        subprocess.call(['myprogram.exe', '-P', arg], stdout=someFile)

Therefore I defined the following function:

def noWinConsole(program):
    if platform.system() == 'Windows':
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        subprocess.call(program, startupinfo=startupinfo)
    else:
        subprocess.call(program)

But when I call it like this noWinConsole(['myprogram.exe', '-P', arg], stdout=someFile) , I get an error because of stdout=somefile:

TypeError: noWinConsole() got an unexpected keyword argument 'stdout'

How can I fix this ?

yaka
  • 107
  • 2
  • 6

2 Answers2

3

noWinConsole is just a wrapper, and as it stands it's expecting just a single argument, program. Thus it freaks out when you give it two arguments (program as the first positional argument, and then the keyword argument stdout intended for the subprocess call).

Luckily, there's a standard idiom for producing a wrapper that passes along any set of arguments to an inner function. It uses the * and ** notation for argument packing/unpacking.

def noWinConsole(*args, **kwargs):
    # Other stuff here as needed
    subprocess.call(*args, **kwargs)

What this does is basically this. The * operator in the function definition takes all the input positional arguments, and packs them into a tuple called args. Then when you call subprocess, the * operator unpacks that tuple, passing in those same arguments in the same order to the next function.

The ** operator works essentially the same way, except that it catches keyword arguments like stdout='foo.txt' and packs all of them into a dictionary like {'stdout':'foo.txt'}. This dictionary is then unpacked and converted back into keyword arguments when you use the ** again in your call to the inner function.

Community
  • 1
  • 1
Henry Keiter
  • 16,863
  • 7
  • 51
  • 80
  • Thank you for you answer, I updated the question with the corrected code, does it seem right to you ? – yaka Mar 29 '14 at 17:09
  • @yaka That seems like it will work for your use case, yes. Though it might be clearer later on to use single-star unpacking for the first argument as well. Also, it's generally frowned upon to edit an answer into a question; it murks things up for future visitors. So you should probably remove that. Glad I was able to help! – Henry Keiter Mar 29 '14 at 17:39
  • @eryksun Yes indeed! I actually considered adding a mention to `partial` here, but I decided against it since it's kind of tangential to the question that the asker really needed answered ("why doesn't Python automatically pack any number of arguments into a single parameter if that's what's in the function signature"). – Henry Keiter Mar 31 '14 at 03:30
1

The function noWinConsole only expects one argument, and when you call it:

noWinConsole(['myprogram.exe', '-P', arg], stdout=someFile)

you are passing two arguments:

  • a list
  • a keyword argument stdout = someFile

How to fix this?

You can do this two ways:

  • Define your method to get kwargs. Note that you are not using the parameters stdout

    def noWinConsole(program, **kwargs):
    
  • Call the function just passing one argument:

    noWinConsole(['myprogram.exe', '-P', arg])
    
Christian Tapia
  • 33,620
  • 7
  • 56
  • 73