maintain 2 versions of python on my computer

Alf P. Steinbach alfps at start.no
Sat Jan 16 05:59:42 EST 2010


* Gertjan Klein:
> 
> What I've been thinking about is to write a single [Windows] executable that
> gets associated with .py and .pyw (instead of python.exe itself).

Well, you need two: one for console subsystem, and one for GUI subsystem.

Happily you can use the same source code. :-)


> This
> executable would parse the #! line to look for a specific python
> version, or use a configured default if none found (or a parsing error
> occurs). It would then invoke the appropriate python version (with
> whatever arguments, if any, are supplied).
> 
> As far as I can see, this allows both typing the script name and
> arguments (i.e., without python31 before it) from a command prompt, and
> doubleclicking on a .py or .pyw file from windows explorer. In both
> cases, the proper python executable would be used to run the script.
> 
> What's been holding me back so far is that probably needs to be written
> in C, to prevent the Python runtime's startup overhead. I haven't
> written any significant amount of C code in years, if not decades, so
> that seems like a daunting task to me at the moment. ;-)

If it's OK with C++, I just sat down and wrote this.

It's not especially well tested (or almost not at all), and it's limited.

It handles or is meant to handle Unicode script file paths, but the path to the 
Python interpreter, specified in a "#!" comment in the script's first line, must 
be single byte per character. And if that path contains spaces must be quoted.


<code file="run_script.cpp">
// Note: in order to handle Unicode paths needs to use Windows API command line.
//
// If this code works then it was written (but not tested) by Alf P. Steinbach.
// Otherwise it's someone impersonating me.

#include    <string>        // std::wstring
#include    <vector>        // std::vector
#include    <stdexcept>
#include    <stdio.h>
#include    <stddef.h>

#undef  STRICT
#undef  NOMINMAX
#undef  UNICODE
#define STRICT
#define NOMINMAX
#define UNICODE
#include    <windows.h>     // CommandLineToArgvW, GetCommandLine

using namespace std;


//------------------------------ Various things ordinarily from libraries...

bool throwX( char const s[] ) { throw std::runtime_error( s ); }

typedef ptrdiff_t       Size;

template< typename Container >
Size n_elements( Container const& c ) { return c.size(); }

// The C++98 standard library doesn't offer Unicode filename functionality.
// Using library extension that works with GNU g++ and Microsoft Visual C++.
class TextFileReader
{
private:
     FILE*   f;
     TextFileReader( TextFileReader const& );            // No copy constructor.
     TextFileReader& operator=( TextFileReader const& ); // No assignment.

public:
     TextFileReader( wstring const& path )
     : f( _wfopen( path.c_str(), L"r" ) )
     {
         (f != 0) || throwX( "Unable to open file for reading" );
     }

     ~TextFileReader() { fclose( f ); }

     wstring line()
     {
         wstring s;
         for( ;; )
         {
             int const c = fgetc( f );
             if( c == EOF || c == L'\n' ) { break; }
             s.push_back( wchar_t( c ) );
         }
         return s;
     }
};

wstring substring( wstring const& s, Size const i, Size const n = -1 )
{
     wstring::size_type const    count   = (n == -1? wstring::npos : n);
     return (i >= n_elements( s )? L"" : s.substr( i, count ));
}

//------------------------------ Main

typedef wstring         String;
typedef vector<String>  StringVector;


StringVector cmdArguments()
{
     struct Args
     {
         wchar_t**   p;
         int         n;

         Args()
         {
             p = CommandLineToArgvW( GetCommandLine(), &n );
             (p != 0) || throwX( "Unable to obtain command line arguments" );
         }

         ~Args() {  GlobalFree( p ); }
     };

     Args const  args;
     return StringVector( args.p, args.p + args.n );
}

int run( wstring const& prog_path, wstring const& args )
{
     wstring cmd_line    = prog_path + L" " + args;

     cmd_line.c_str();
     PROCESS_INFORMATION     process_info    = {};
     STARTUPINFO             startup_info    = { sizeof( startup_info ) };
     bool const ok           = !!CreateProcess(
         0,              // application name
         &cmd_line[0],   // command line
         0,              // process security attributes
         0,              // thread security attributes
         TRUE,           // inherit handles
         0,              // creation flags
         0,              // environment, 0 => inherit parent process env.
         0,              // current directory
         &startup_info,
         &process_info
         );
     (ok)
         || throwX( "Unable to run the interpreter" );


     bool const  wait_ok =
         (WaitForSingleObject( process_info.hProcess, INFINITE ) != WAIT_FAILED);

     DWORD   exit_code   = EXIT_FAILURE;
     GetExitCodeProcess( process_info.hProcess, &exit_code );
     CloseHandle( process_info.hProcess );
     CloseHandle( process_info.hThread );

     (wait_ok) || throwX( "Waiting for the program to end failed" );
     return exit_code;
}

int cppMain()
{
     StringVector const  args    = cmdArguments();
     (n_elements( args ) == 2)
         || throwX( "Usage: run_script QUOTED_PATH_TO_SCRIPT_FILE" );

     wstring const   script_path = args[1];
     wstring const   first_line  = TextFileReader( script_path ).line();
     wstring const   prefix      = substring( first_line, 0, 2 );
     wstring const   prog_path   = substring( first_line, 2 );
     (prefix == L"#!" && prog_path.length() > 0)
         || throwX( "Unable to determine interpreter" );

     return run( prog_path, script_path );
}

int main()
{
     try
     {
         return cppMain();
     }
     catch( exception const& x )
     {
         fprintf( stderr, "!run_script: %s\n", x.what() );
         return EXIT_FAILURE;
     }
}
<code>


<build compiler="g++">
g++ -std=c++98 -pedantic run_script.cpp -o run_script.exe
g++ -std=c++98 -pedantic run_script.cpp -Wl,-subsystem,windows -o run_scriptw.exe
</build>


<build compiler="msvc">
cl /nologo /GX /GR run_script.cpp shell32.lib
cl /nologo /GX /GR run_script.cpp shell32.lib /Fe:run_scriptw.exe /link 
/subsystem:windows /entry:mainCRTStartup
run_script.cpp
</build>


<code file="test.py">
#!python

print( "Hello" )
exit( 123 )
</code>



Cheers & hth.,

- Alf



More information about the Python-list mailing list