1

So I'm working on a chemistry project for fun, and I have a function that initializes a list from a text file. What I want to do s make it so the function replaces itself with a list. So here's my first attempt at it which randomly will or won't work and I don't know why:

def periodicTable():
    global periodicTable
    tableAtoms = open('/Users/username/Dropbox/Python/Chem Project/atoms.csv','r')
    listAtoms = tableAtoms.readlines()
    tableAtoms.close()
    del listAtoms[0]
    atoms = []
    for atom in listAtoms:
        atom = atom.split(',')
        atoms.append(Atom(*atom))
    periodicTable = atoms

It gets called in in this way:

def findAtomBySymbol(symbol):
    try:
        periodicTable()
    except:
        pass
    for atom in periodicTable:
        if atom.symbol == symbol:
            return atom
    return None

Is there a way to make this work?

4
  • 1
    Don't make the function replace itself with a list. Using a list and a function is different - how do you call a list or slice a function? - and trying to deal with this cases differently is messy as the using code shows. Also, "randomly" isn't a good error description - not swallowing all exceptions might give a better failure indicator. Commented Oct 13, 2013 at 18:59
  • You may be interested in "memoization functions", although I would just do: periodicTable = loadPeriodicTable() (say once, at the start of the program) myself as there is no obvious need for caching or memoization here. Commented Oct 13, 2013 at 19:01
  • @user2864740 Yeah, I thought about doing that, but I really enjoy exploring intricacies of python, much to the annoyance of some users here. Commented Oct 13, 2013 at 19:06
  • 1
    Then see one of the various memoization/cache answers shown. With memoization/caching the same name is always bound to a function, however. Thus it would always used like a function - for atom in periodicTable() - (with a function call and) with the difference being that subsequent invocations return the cached data without needing to reload from the file. Commented Oct 13, 2013 at 19:07

2 Answers 2

4

Don't do that. The correct thing to do would be using a decorator that ensures the function is only executed once and caches the return value:

def cachedfunction(f):
    cache = []
    def deco(*args, **kwargs):
        if cache:
            return cache[0]
        result = f(*args, **kwargs)
        cache.append(result)
        return result
    return deco

@cachedfunction
def periodicTable():
    #etc

That said, there's nothing stopping you from replacing the function itself after it has been called, so your approach should generally work. I think the reason it doesn't is because an exception is thrown before you assign the result to periodicTable and thus it never gets replaced. Try removing the try/except block or replacing the blanket except with except TypeError to see what exactly happens.

Sign up to request clarification or add additional context in comments.

4 Comments

Nice. A little more upfront work, but less confusion with mutable defaults. :)
@EthanFurman Yeah, this has the advantage of being reuseable and keeping the original function signature. If it's just for a small throwaway script your solution could be preferable...
Nitpick: It's only keeping the original signature because there isn't one; if there was it would be much more complicated to keep it. Check out Michele Simianoto's Decorator module if you want to see the gory details.
@EthanFurman Yeah, when you consider introspection that's a different matter. I meant it in the way that you'll still get a TypeError when you call it with a wrong signature, which I'd usually consider good enough.
3

This is very bad practice.

What would be better is to have your function remember if it has already loaded the table:

def periodicTable(_table=[]):
    if _table:
        return _table
    tableAtoms = open('/Users/username/Dropbox/Python/Chem Project/atoms.csv','r')
    listAtoms = tableAtoms.readlines()
    tableAtoms.close()
    del listAtoms[0]
    atoms = []
    for atom in listAtoms:
        atom = atom.split(',')
        atoms.append(Atom(*atom))
    _table[:] = atoms

The first two lines check to see if the table has already been loaded, and if it has it simply returns it.

2 Comments

This won't work. The assignment to _table at the end won't persist beyond the function scope. Instead, the list (originally named by _table) needs to be modified.
@user2864740 is right, this should be _table.extend(atoms) or _table[:] = atoms - you can't change the reference of the default argument.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.