Jacob
Jacob

Reputation: 7821

is it possible to use a lambda as a dictionary default?

I'm trying to keep a dictionary of open files for splitting data into individual files. When I request a file from the dictionary I would like it to be opened if the key isn't there. However, it doesn't look like I can use a lambda as a default.

e.g.

files = {}
for row in data:
  f = files.get(row.field1, lambda: open(row.field1, 'w'))
  f.write('stuff...')

This doesn't work because f is set to the function, rather than it's result. setdefault using the syntax above doesn't work either. Is there anything I can do besides this:

f = files.get(row.field1)
if not f:
    f = files[row.field1] = open(row.field1, 'w')

Upvotes: 5

Views: 627

Answers (5)

Marcin
Marcin

Reputation: 49856

This is the exact reason why dict[key] syntax raises KeyError:

files = {}
for row in data:
  f = files.get(row.field1, lambda: open(row.field1, 'w'))
  f.write('stuff...')

should become:

files = {}
for row in data:
  try: f = files[row.field1]
  except KeyError: f = open(row.field1, 'w')
  f.write('stuff...')

Upvotes: 1

Niklas B.
Niklas B.

Reputation: 95328

This use case is too complex for a defaultdict, which is why I don't believe that something like this exists in the Python stdlib. You can however easily write a generic "extended" defaultdict yourself, which passes the missing key to the callback:

from collections import defaultdict

class BetterDefaultDict(defaultdict):
  def __missing__(self, key):
    return self.setdefault(key, self.default_factory(key))

Usage:

>>> files = BetterDefaultDict(lambda key: open(key, 'w'))
>>> files['/tmp/test.py']
<open file '/tmp/test.py', mode 'w' at 0x7ff552ad6db0>

This works in Python 2.7+, don't know about older versions :) Also, don't forget to close those files again:

finally:
  for f in files.values(): f.close()

Upvotes: 7

Geoff Reedy
Geoff Reedy

Reputation: 36051

You can use defaultdict from the collections module

class FileCache(collections.defaultdict):
  def __missing__(self, key):
    fo = open(key, 'w')
    self[key] = fo
    return fo

Though it might be better to just do

files = {}
def get_file(f):
  fo = files.get(f)
  if fo is None:
    fo = open(f, 'w')
    files[f] = fo
  return fo

for row in data:
  f = get_file(row.field1)
  f.write('stuff...')

Upvotes: 1

Hamish
Hamish

Reputation: 23346

Another option for a subclass that should do what you need:

class LambdaDefaultDict(dict):

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default()

    def setdefault(self, key, default=None):
        if not self.has_key(key):
            self[key] = default() if default else None
        return self[key]

Or, perhaps more general - to allow defaults that are values or callable expressions:

class CallableDefaultDict(dict):

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default() if callable(default) else default

    def setdefault(self, key, default=None):
        if not self.has_key(key):
            self[key] = default() if callable(default) else default
        return self[key]

Upvotes: 1

Russell Borogove
Russell Borogove

Reputation: 19047

You could wrap the get-and-open in a class object's __getitem__() pretty easily - something like:

class FileCache(object):
    def __init__(self):
        self.map = {}

    def __getitem__(self,key):
        if key not in self.map:            
            self.map[key] = open(key,'w')
        return self.map.key

Upvotes: 2

Related Questions