I have a python script using Tkinter. In the script I have a numeric variable that the user is able to change the value of by entering the value in an entry box. I presently have the script reading and writing the value to an external .txt file so when the program is exited and restarted the previous value of the variable is retained. I have been unsuccessful in finding a way to store the value inside the script file. Is this possible? If so, could someone point me in the direction of a way to do this so the script would not have to depend on an external file?
Thanks

Recommended Answers

All 16 Replies

This does the thing, I do not know how good programming style it is to change own code, though:

## My program
import sys
def between(left,right,s):
    before,_,a = s.partition(left)
    a,_,after = a.partition(right)
    return before,a,after

my_value=2160278.18574
print 'Old value',my_value
my_value=1.2*my_value
print 'New value', my_value
my_code=open(sys.argv[0]).read()
beginning,value,rest=between('my_value=','\n',my_code)
open(sys.argv[0],'w').write(beginning + 'my_value=' + str(my_value)  + '\n' + rest)
input('Ready')

Either externally in a configuration file or database, or in the registry for Windows. There is no other way.

EDIT: or environment variables

I have elected to use the external file. Now I have another issue I cannot find an answer to.
If I place the script and the text file in \usr\local\bin the script cannot find the text file unless I put the path in the script file. I would like not to have to hard code the path. I have read much about sys paths, search paths, pythonpaths, etc in the last few day and it seems the more I read the more confused I get.
How would I do this so the program will work in whatever directory it is placed in?
Thanks
Frank

I think there is a way to get the path to the .py file. sys.argv[0] might work.
I'm not sure though.

Thanks to both of you for the suggestions. Here is what I did. It seems to work so far, will see what happens.
Frank

###Import modules
from Tkinter import *
import time, datetime, os, re, tkMessageBox
from urllib import urlopen

## This ties the cfg file to the script directory
cfgfile = '/HnUsageMeter.cfg'
pathname = os.path.dirname(sys.argv[0])
filename = pathname+cfgfile

###set value of allowance_limit variable
global allowance_limit
linestring = open(filename, 'r').read()
allowance_limit = int(linestring)

###loop to get info for plan

Why do you need the global statement?

Why do you need the global statement?

Good question. :)
At one point, before the latest additions, the script would crash without it. I have made a bunch of changes since then. I took it out just now and it seems to work just fine with out it.
I am just trying to learn to "program" and am not very good at it.
Thanks
Frank

Good question. :)
At one point, before the latest additions, the script would crash without it. I have made a bunch of changes since then. I took it out just now and it seems to work just fine with out it.
I am just trying to learn to "program" and am not very good at it.
Thanks
Frank

That's good.
The global statement is bad, and it usually signals some bad code structure.

import configparser
import os
import sys

config = configparser.ConfigParser()

#If the script is executed __name__ will be '__main__' and sys.argv[0] will be the full path of the script.
if __name__ == '__main__':
    path = os.path.dirname(sys.argv[0])
#Else the script was imported as a module and it has a __file__ attribute that will be the full path of the script.
else:
    path = os.path.dirname(__file__)

#The configuration parser reads the file at the full path made from joining the base path and the file name.
config.read(os.path.join(path, 'HnUsageMeter.cfg'))

allowancelimit = int(config.get('DEFAULT', 'allowancelimit', 0))
""";HnUsageMeter.cfg
;In cfg files use ; to comment out a line.
;The DEFAULT section supplies default values for options in all sections.
;If an option of the same name is not overridden in other sections then all options of the same name will have the same value.
[DEFAULT]
allowancelimit=1"""

Response to tonyjv: it is a horrible idea to self-modify your code. You can make wonderful messes, though it is kind of fun if you don't have to maintain it. I *think* that the fact the script is byte compiled means you can get away with writing to the .py file on the OSs which would prevent modifying an open file.

Response to those who want to use a path relative to the script file: That file may not live in a place that is writable. Also: lrh9: os.path.dirname(sys.argv[0]) fails if the script was invoked via a symbolic link.

For unix-like OSs, the best option (still not entirely foolproof) is to use os.getenv('HOME') and create a file there. To add suspenders to the belt, fail over to a tmp location if there is no HOME or it isn't writable. A file whose name starts with '.' will not be seen by most users most of the time. For Windows OSs,that will work too, but storing the datum in the user's registry is the way the OS expects things to be done.

commented: Some good catches. Some bad advice. Otherwise good reply. +1

Using files can be a better option than using the registry for storing settings in certain situations, especially when users may wish to alter application settings externally and save them for later use.

Storing settings in files can also provide greater cross platform support, especially when the user may wish to transfer settings to a new platform.

The registry has its place, but it isn't a settings panacea.

Also, cluttering a home folder with various settings files doesn't seem like a good idea. It violates the principles of organization and privacy.

How to handle symbolic links:

import os, sys, configparser

config = configparser.ConfigParser()

#Ran as a script?
if __name__ == '__main__':
    #Here by symbolic link?
    if os.path.islink(sys.argv[0]):
        try:
            #Read the path to which the link points.
            link = os.readlink(sys.argv[0])
        except:
            """Insert code to handle if it is a link but reading the link fails."""
            pass
        else:
            if not os.path.isabs(link):
                #If the link was to a relative path need to do some extra work.
                path = os.path.dirname(os.path.join(os.path.dirname(sys.argv[0]), link))
            else:
                #Else assume path OK.
                path = os.path.dirname(link)
    #Else assume path OK.
    else:
        path = os.path.dirname(sys.argv[0])
#Else imported.
else:
    path = os.path.dirname(__file__)

config.read(os.path.join(path, 'HnUsageMeter.cfg'))

allowancelimit = int(config.get('DEFAULT', 'allowancelimit', 0))

You seem to know about symbolic links. If the path read from a link is an absolute path will it be a complete path or will it lack a root?

In windows .directory is slightly nasty as the name can not be edited in Explorer and the directory is visible in Windows.

Yes the meta-programming is difficult to do in disciplined way but have its uses, mostly by way of using eval in program generated structures or executing fully program generated code (for example testing of genetic programming program for fitness). Generally it is stupid idea however to pick a piece of code in main module and do transformation to code, only to not have external files. You can only do simple open('config').read() and there is your value minus maybe conversion to number.

lrh9: Also, cluttering a home folder with various settings files doesn't seem like a good idea. It violates the principles of organization and privacy. On the contrary, it nicely matches the principles of organization and privacy: If you and I both run the program on the same computer, we should have individual and private settings. (Some subdirectory of) $HOME is the normal, therefore organized, place to put such config files. Since I can adjust my own file permissions in $HOME, I also have as much privacy about this as I prefer.

If, on the other hand, the program should run as a daemon (unix terminology) or (I think) "service" (Windows terminology), then obviously, $HOME is the wrong place (whose $HOME?). In that case, the usual practice (unix-like OSs) is to put the config file in an /etc directory. I think that the Windows practice is for these to be in the registry and to provide some editor function as needed. (Providing an options editor applies, more or less equally, to most "user friendly" OSs.)

lrh9: You seem to know about symbolic links. If the path read from a link is an absolute path will it be a complete path or will it lack a root? I don't know what a "complete" path is. I put this program in one place, provided a symbolic link in another place on my PATH and invoked it a variety of ways:

#!/usr/bin/env python
import os
import sys
print ("%s: %s"%(sys.argv[0],os.path.dirname(sys.argv[0])))
  • Invoked on my PATH by its own name (ar.py): ./ar.py: . note the added '.' which is on my PATH
  • Invoked on my PATH by its symlink (WOO): /Users/griswolf/bin/WOO: /Users/griswolf/bin note the added '/Users/griswolf/bin' which is on my path
  • Invoked off my path by name (mysite/ar.py): mysite/ar.py: mysite note no added directory
  • Invoked off my path by symlink(bin/WOO): bin/WOO: bin note no added directory

If you want the absolute path, which in my opinion is always safest, then you should use os.path.abspath(anypath)

Of course users must have their own folder for their own settings. I didn't know that the home folder was that folder on Unix based systems. On Windows we have "\Documents and Settings\User\Application Data", "\Documents and Settings\User\Local Settings" and "\Documents and Settings\User\Local Settings\Application Data". However, we shouldn't just dump settings files into either. They should be separated by sub-directories named after their application.

Did you try my second code using your links?

OK. So to feel 'natural' to the user, config files should go in an OS dependent place. I'll take your word for the Windows "natural place". In unix-like OSs, one natural place is $HOME/.something which might also be considered 'classic unix' layout. OS/X seems to like $HOME/Library/Application_name/... or $HOME/Library/Application Support/Application_name

I did not try your second code as I disagree with the usefulness (per my belief that usually should be user-centric storage). It looks a little more complex than I think it would need to be, but I didn't work it out for myself, so it might be perfect. I'm not sure about the relative symbolic link calculation: Probably right, but I would need to think it through a little harder.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.