Python Dictionaries

lua-users home
wiki

[!] VersionNotice: The below code pertains to an older Lua version, Lua 4. Certain features used like tag methods (settagmethod) are no longer present in Lua 5 but have been replaced with metamethods.

Introduction

Lua's table type is a multipurpose container which is somewhere in between Pythons list and dictionary types. The listing below emulates Pythons dictionaries. See the documentation on Pythons mapping types to what is implemented here [1]. See PythonLists and ClassesAndMethods for more information on how it is implemented. The script was written for Lua version 4.0.

This code was an exercise in trying to emulate the functionality of Python using Lua. It threw up some problems with Lua which seem to have been resolved in Lua 4.1. Comments on this implementation :-

Sorry, this will not work. Try:
foo = Dict:new { len="vcbvcb" }
print(foo:len())
The colon operator accesses foo, not Dict. You redirect missing elements (methods) to Dict but if the name already exist in foo that one will be taken. Read [2] for a possible (already implemented and working) solution. -- ET

So a solution is to add:
settagmethod(tag(Dict), "gettable", 
          function(t,k)
            if rawget(Dict,k) then
              return rawget(Dict,k)
            else
              return rawget(t,k)
            end
          end )
which stops the conflict between user data and the dict implementation. --NDT

Sorry, it will not stop the conflict. You just swap the priorities; the conflict is still there. Now foo:len() will give 1, but foo.len will give a function, not the stored value "vcbvcb". Btw, PythonLists only works because there are no collisions between user indexes (always numbers) and methods (always strings). -- ET

Code:

-- Emulation of Python dictionaries
-- Nick Trout -- thanks to lhf & ET
-- See http://www.python.org/doc/current/lib/typesmapping.html
-- $Header: /Tools/build/pydict.lua 3     11/09/01 14:20 Nick $

Dict = settag({},newtag())

-- handle access to our dictionary table type
function Dict._gettable(t,k)
  -- print("gt",t,k)
  -- See if the key we are looking for is a method in Dict.
  -- Note the user may have used a key which has the same name as
  -- one of our functions, but methods take precidence . eg.
  -- foo = Dict:new { len="vcbvcb" }  print(foo:len())
  local v = rawget(Dict,k)
  if v then
    return v
  else
    -- In Python, if we dont find a key in a dictionary we raise a Key error.
    v = rawget(t,k)
    assert(v,"Key error")
    return v
  end
end

settagmethod(tag(Dict), "gettable", Dict._gettable)

-- Create a new dictionary.
-- eg. dict = Dict:new()  or  dict = Dict:new{ a=1,b=2 }
function Dict:new(t)
  if not t then t={} end
  settag(t,tag(Dict))
  return t
end

-- len(a) the number of items in a
function Dict:len()
  -- Note: Lua returns the number of indexed objects, not mapped objects with getn
  local cnt=0
  for k,v in self do cnt=cnt+1 end
  return cnt
end

-- Python: a[k] the item of a with key k (1)
-- Lua: dict[k]

-- Python: a[k] = v set a[k] to v
-- Lua: dict[k] = v

-- Python: del a[k] remove a[k] from a (1)
-- Lua: dict:del(k)
function Dict:del(k)
  self[k] = nil
end

-- Python: a.clear() remove all items from a
-- Lua: dict:clear()
function Dict:clear()
  -- cannot do self = {} as self passed by value
  -- we cannot change a table inside a for loop
  -- eg.  for k,v in self do self[k]=nil end
  -- Must collect keys and delete them thus:
  local t={}
  for k,v in self do t[k]=1 end
  for k,v in t do self[k]=nil end
end

-- Python: a.copy() a (shallow) copy of a
-- Lua: dictcopy = dict:copy()
function Dict:copy()
  local d = Dict:new()
  for k,v in self do d[k] = v end
  return d
end

-- Python:  k in a 1 if a has a key k, else 0
-- k not in a 0 if a has a key k, else 1
-- a.has_key(k) Equivalent to k in a
function Dict:has_key(k)
  return self[k]  -- return value for true, or nil for false
end

-- Python: a.items() a copy of a's list of (key, value) pairs
function Dict:items()
  local items={}
  for k,v in self do tinsert(items,{k,v}) end
  return items
end

-- Python: a.keys() a copy of a's list of keys
function Dict:keys()
  local keys={}
  for k,v in self do tinsert(keys,k) end
  return keys
end

-- Python: a.update(b) for k in b.keys(): a[k] = b[k]
-- Add b to a
function Dict:update(b)
  assert(type(b)=="table")
  for k,v in b do self[k] = v end
end

-- Python: a.values() a copy of a's list of values
function Dict:values()
  local vals={}
  for k,v in self do tinsert(vals,v) end
  return vals
end

-- Python: a.get(k[, x]) a[k] if k in a, else x
-- Return the value associated with key k or x is key not found
function Dict:get(k,x)
  -- use rawget to avoid invoking "index" tag method if k not found
  return rawget(self,k) or x
end

-- Python: a.setdefault(k[, x]) a[k] if k in a, else x (also setting it)
-- Set value for k to x if k not found, also return value
function Dict:setdefault(k,x)
  self[k] = rawget(self,k) or x
  return self[k]
end

-- Python: a.popitem() remove and return an arbitrary (key, value) pair
function Dict:popitem()
  local k,v = next(self)
  self[k] = nil
  return k,v
end

-- Python len(list) is not the same as getn, must count key-value pairs
len = Dict.len

-- test using: lua -f pydict.lua -test
if arg and arg[1]=="-test" then
  local prl = function(l) for i=1,getn(l) do write(l[i]) end print() end
  local prd = function(l) for k,v in l do write(k.."="..v..",") end print() end
  local dict = Dict:new{a=1,b=2,c=3}  prd(dict)
  dict["d"]=4 ; write("d=4: ") ; prd(dict)
  dict.e=5 ; write("e=5: ") ; prd(dict)
  print("dict length: "..dict:len())
  dict:del(3) ; write("del[3]: ") ; prd(dict)
  local d2 = dict:copy() ; write("copy: ") ; prd(d2)
  d2:clear() ; write("clear: ") ; prd(d2)
  print("length: "..d2:len())
  assert( d2:len()==0 )
  assert( dict:has_key("a") )
  print('dict:has_key("a") : '..dict:has_key("a"))
  write("items: ") ; print( getn(dict:items()) )
  write("keys: ") ; prl( dict:keys() )
  dict:update{ f=6,g=7 } ; write("dict:update{ f=6,g=7 } : ") ; prd(dict)
  write("values: ") ; prl( dict:values() )
  write('dict:get("z",26) : ') print(dict:get("z",26))
  write('dict:setdefault("y",25) : ') print(dict:setdefault("y",25))
  write('dict:popitem() : '..dict:popitem()..", ") ; prd(dict)
  local foo = Dict:new { len="vcbvcb" }  print(foo:len())  -- same name test
  -- print(foo["wont find this"]) -- test "key error"
end


See also: PythonLists
RecentChanges · preferences
edit · history
Last edited January 6, 2007 5:06 am GMT (diff)