rfc: a self-editing script

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Sat Oct 10 09:58:58 CEST 2009


On Fri, 09 Oct 2009 23:30:16 +0000, gb345 wrote:

> The following fragment is from a tiny maintenance script that, among
> other things, edits itself, by rewriting the line that ends with '###
> REPLACE'.
> 
> ######################################################################
> 
> import re
> import fileinput
> 
> LAST_VERSION = 'VERSION 155' ### REPLACE
> 
> service = Service(url='http://url.to.service')
> 
> if service.version_string == LAST_VERSION:
>     sys.exit(0)
> 
> for line in fileinput.input(sys.argv[0], inplace=True):
>     if re.search(r"### REPLACE$", line):
>         print ("LAST_VERSION = '%s' ### REPLACE" %
>                service.version_string)
>     else:
>         print line,
> 
> # ...and goes on to do more stuff...
> 
> ######################################################################
> 
> This script is meant to run periodically (via cron), and "do more stuff"
> whenever the fetched value of service.version_string differs from what
> it was at the time of the script's prior invocation. (The interval of
> time between such changes of value varies from one change to the next,
> but it is always of the order of several weeks.)
> 
> Hence this script needs to preserve state between invocations. The
> rationale for the acrobatics with fileinput above is to make this script
> completely self-contained, by circumventing the need some external means
> (e.g.  a second file, or a DB) of preserving state between invocations.
> 
> Is there a better way to circumvent the requirement for an external
> repository of state information?

Yes -- change the requirement. What's wrong with having a separate file 
to store state?

Self-modifying code is almost always the wrong solution, unless the 
problem is "how do I generate an unmaintainable mess?".

But if you absolutely have to write to the program file, then append your 
data to the end of the file (as a comment) and later read that, rather 
than modifying the actual code in place. That is, you fetch the 
LAST_VERSION by reading the last non-empty line in the file, something 
like this:


# Untested
def get_last_version(filename):
    """Retrieves the last version number from the given filename, 
    taken from the last non-empty line."""
    candidate = ''
    for line in open(filename, 'r'):
        line = line.strip()
        if line and line.startswith('#'):
            candidate = line.lstrip('# \t')
    # error checking goes here
    return candidate

LAST_VERSION = get_last_version(sys.argv[0])

...
more code goes here
...


# ==================================================
# ===      Version number history goes here.     ===
# === DO NOT insert any code after this point!!! ===
# ==================================================
# 1.0.1
# 1.0.2a
# 1.0.2
# 1.0.5


This has the added advantage that you can track the updates made to the 
version number.



-- 
Steven



More information about the Python-list mailing list