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
while
loop andfor
loops if
/else
statements- Simple variable assigment:
has_more = True
- Boolean value of
True
just 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, bothNone
andnil
are singleton objects, and appear to be used in similar ways.2is None
apparent equivalent ofnil?
andis not None
equivalent 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