python to ruby translation no. 1
16 Jul 2014
Yesterday, I was browsing through the Dropbox docs, developer forum and blog, trying to find a method or endpoint that returns multiple file results at once (not just the file path or name, but the actual file). I wasn't able to find what I was looking for, but in the process, I came across one of their dev blog posts about delta(). It contained a lot of information, some of it potentially useful to me, but at also had examples in Python.
I had seen Python before, but I'd never taken a good look. Now here I was, faced with some pieces of code that might yield some nuggets of gold -- if I wanted to decipher the example.1 Honestly, I was excited by the prospect. This was a chance to learn some Python, maybe learn more about Ruby in the process, and do some fun translation work.
I started with this chunk of Python and started my Ruby interpretation:
def list_files(client, files=None, cursor=None):
if files is None:
files = {}
has_more = True
while has_more:
result = client.delta(cursor)
cursor = result['cursor']
has_more = result['has_more']
for lowercase_path, metadata in result['entries']:
if metadata is not None:
files[lowercase_path] = metadata
else:
# no metadata indicates a deletion
# remove if present
files.pop(lowercase_path, None)
# in case this was a directory, delete everything under it
for other in files.keys():
if other.startswith(lowercase_path + '/'):
del files[other]
return files, cursor
The most obvious thing I noticed was the use of white space to close each code block. So my first step was to go in and add 'end' as necessary. Then I started to identify things that looked familiar to me:
- Accepting arguments when defining a method and assigning defaults to them in the first line
- Setting up an empty hash with just
files = {}and subesquently getting values from the hash with the square brackets syntax, e.g.result['cursor'] - A
whileloop andforloops if/elsestatements- Simple variable assigment:
has_more = True - Boolean value of
Truejust a capitalized version of Ruby'strue
Then were the things that were slightly different but I could understand by analogy:
None: I immediately assumed this was the rough equivalent to Ruby'snil, but did a quick Google search to confirm. Yes, bothNoneandnilare singleton objects, and appear to be used in similar ways.2is Noneapparent equivalent ofnil?andis not Noneequivalent of!nil?- Beginning a block with
:corresponds with Ruby'sdo .startswith()matches Ruby's.start_with?()
That pretty much left only two things that had yet to be translated: files.pop(lowercase_path, None) and del files[other]. pop looked dangerously similar to Ruby's Array instance method #pop, but since this was being called on a hash I suspected it wasn't quite the same. After some investigation (i.e. a Google search for "python hash pop"), I found out a couple new things -- first, files wasn't exactly a hash as I know it, but this thing called a dictionary. dict.pop(key[, default]) is function you can call on a dictionary (like a Ruby hash) to remove a key that exists (and matches the first argument) and return its value; and if the key doesn't exist, return the default supplied as the second argument. If the key doesn't exist and you don't provide a default value as the second argument, then a KeyError is raised.
This function is similar to Ruby's Hash#delete(key), but delete(key) assumes more than dict.pop(key[, default]) does -- if the key doesn't exist, it automatically returns nil or the already given default value of the hash,4 instead of raising an error or exception. So here, files.pop(lowercase_path, None) was the equivalent of files.delete(lowercase_path).
That just left del files[other], which apparently is also the rough equivalent of Ruby's files.delete[other], with one noticeable difference being that Python's del statement doesn't return anything, whereas Ruby's method will return the value of other or the hash's default.5 That was pretty interesting to notice -- using this awesome in-browser interpreter to try out this Python business,6 I noticed that some functions in Python just don't return a value back to you, whereas Ruby methods always do.7
Here's my Ruby version of the above. It's pretty literal; I didn't break it out into smaller methods or objects at all, and I didn't try to pay too much attention to idioms because I don't know anything about Python idioms yet.
def list_files(client, files=nil, cursor=nil)
if files.nil?
files = {}
end
has_more = true
while has_more do
result = client.delta(cursor)
cursor = result['cursor']
has_more = result['has_more']
for lowercase_path, metadata in result['entries'] do
if metadata # literally, if !metadata.nil?
files[lowercase_path] = metadata
else
# no metadata indicates a deletion
# remove if present
files.delete(lowercase_path)
# in case this was a directory, delete everything under it
for other in files.keys() do
if other.start_with?(lowercase_path + '/')
files.delete(other)
end
end
end
end
end
return files, cursor
end