Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Probably half of the commenters here know this, but since we're here, this is my go-to boilerplate for starting a python script. (Probably won't work on Windows.)

    #!/bin/sh
    # Run the interpreter with -u so that stdout isn't buffered.
    "exec" "python3" "-u" "$0" "$@"

    import os
    import sys
    curdir = os.path.dirname(os.path.realpath(sys.argv[0]))
    # Add enough .. to point to the top-level project directory.
    sys.path.insert(0, '%s/../..' % curdir)

    Your main program starts here ...


> # Add enough .. to point to the top-level project directory.

This suggests that there is more than one entry point to the Python project?

While I'm sure there are good reasons for this, and while I'm not criticising your instance of this specifically, as a general point of advice I've found this sort of thing to be a bit of an anti-pattern.

Having one entry that handles things like path setup and other global concerns, before delegating out to subcommands or whatever construct works best makes it much easier to keep the whole codebase aligned in many ways.

Django has a system for this and while it has its flaws, it is nice to have it. Using this, on our main Python codebase of ~400k lines, we have a single manual entry point, plus one server entry point. Coordinating things like configuration, importing, and the application startup process, are therefore essentially non-issues for us for almost all development, even though we have a hundred different operations that a dev can run, each of which could have been a separate tool like this.


I have been a huge fan of this issue, too!

For additional bonus points, have your single entry point exhibit a CLI that properly documents everything the developer can do, i.e. what features are available, what environment variables and config flags can be set etc. That way, the code essentially documents itself and you know longer have to keep your README file updated (which people always tend to forget).


I use a very similar flow. The highly-opinionated-yet-effective pattern I use involves pydantic, cleo, and entry_points/console_scripts in setup.py.

- everything is structured as a module

- options and args are stored in their own module for easy reuse

- the whole stack has one cleo.Application, with however many subcommands. Usually of the form "mytool domain verb" e.g. "mytool backend start."

- cleo args/options are parsed into pydantic objects for automatic validation (you could do this with argparse and dataclasses to skip the deps but it's more work)

- each subcommand has a `main(args: CliArgsModel)` which takes that parsed structure and does its thing. This makes it super easy to unit test

I install into a venv with `poetry install` or `pip install -e` for editable installs.

It all just works, no fuss, and so damn modular.


There's no well known module to do most this for you? In perl, the recent canonical way is to use the FindBin module to find the current binary's running dir, and the the local::lib module to set the import path (or just use lib for older style library dirs). That always seemed cumbersome to me at 2-3 lines that weren't very clean looking.

Also, say what you will about Perl and esoteric global variables, but it's kinda nice to be able to toggle buffered output on and off on the fly. Is there really no way to do this in python without re-executing the script like that?


Ya... if you're trying to get the path of the script, you can use `__file__` special variable (instead of loading it from bash $0 and grabbing sys.argv[0]).

For adding current directory to front of path, the sys.path.insert() call is a pretty sound way of doing it.


Yeah, I think you're right - didn't know about __file__.

(To clarify, using "$0" with bash is just standard method to invoke the same script with an interpreter - sys.argv[0] will work with or without bash exec part.)


That's crafty; it reminds me of the suggested similar tactic with tclsh since tcl honors backslashes in comment strings(!): https://wiki.tcl-lang.org/page/exec+magic


Wouldn't this part:

    #!/bin/sh
    "exec" "python3" "-u" "$0" "$@"
Be the same as this?

    #!/usr/bin/env python3 -u


One difference is that parameters in the shebang is non standard and not supported on all OSes. Linux supports it, though.


And I think only one. Tripped me up once.


Why not just add a shebang , chmod +x and then you're done?


    #!/usr/bin/python
This is sometimes what you want, but it will always look at this exact path, and won't play nicely with virtualenv/conda.

    #!/usr/bin/env python
This works - it will use Python found in $PATH. Unfortunately you can't add any more parameters to the interpreter.

The contraption I wrote allows adding arbitrary parameters - I was burnt one too many times by Python silently buffering my debug messages, so I use it to always add "-u".


Reading `man env` in Linux shows that you can use:

  #!/usr/bin/env -S python $ARGS
For example:

  #!/usr/bin/env -S python -i
  
  print("Entering interactive mode...")
I'm not sure if it works in other OS.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: