Ruby to Python Translation Guide
Last Update: 12/12/2019
I wrote this guide to different Ruby idioms and how to do them in Python. Organization is alphabetical. This will have to be a continually updated document as I keep finding holes in my Python knowledge and my prior practice of "Learn it / Blog it" simply doesn't put enough things into the same place. I need a single, easily searchable place to store all this random "how to do X cruft".
This is written as headings which discuss the problem and then how it is done in Ruby and in Python and then, optionally, a Python context. Hopefully this approach makes it useful to both ruby-ites and python-istas.
Note 1: My day to day lingua franca is still Ruby and in a Rails style context where everything is autoloaded. Python to me is a specialized tool for Data Science / Machine Learning. I find that Python:
- Lacks the elegant simplicity of Ruby
- Python syntax makes my eyes want to bleed, can we have an underscore character please, how about a colon or some more parentheses
- Is developer hostile by default
Note 2: These are opinions! I am writing them after a craptastic work day so that and lack of sleep may be tilting my opinions more than a bit; I mean I'm right but still … And please understand that I fundamentally respect that amazing work that Guido did but I find myself in Matz's camp by preference. Still nothing else has the ecosystem that Python does.
Note 3: If you disagree with the above, my twitter handle is fuzzygroup and my email is easily discoverable. Let the fun begin …
Note 4: This was initially written in late 2019 so the Python flavor / version is 3.7 using VirtualEnv on a Mac with pip3.
Note 5: If there's things you want to see here then actually do let me know. This isn't a planned out document, it is more of a "what made me feel stooopid in Python today" type of thing.
Debugging
The ruby is:
byebug
The python is:
pdb.set_trace()
Getting All Attributes of an Object
Most Ruby objects, at least in a Rails context, are generally ActiveRecord objects and respond to .attributes so:
plan = Plan.first
2.6.3 :016 > plan.attributes
{
"id" => 1,
"created_at" => Sat, 27 Jul 2019 08:28:17 UTC +00:00,
"updated_at" => Sat, 27 Jul 2019 08:28:17 UTC +00:00,
"name" => "Write Daily",
"public_name" => nil,
"description" => nil,
"options" => nil,
"user_id" => 1,
"category_id" => nil,
"has_habits" => nil,
"habit_count" => 3,
"shareable" => false,
"master_plan" => false,
"active" => true
}
The python equivalent of this is is to reference the object's internal dict. Below we have an object called message which is coming in from a streaming data solution called sse:
message.__dict__
{'data': '{"all_awardings":[],"approved_at_utc":null,"ups":1,"user_reports":[]}', 'event': 'rc', 'id': '33288433929', 'retry': None}
# There are four different top level things that can be called:
message.data
message.event
message.id
message.retry
# despite the appearance, the attributes of data are NOT json buy just a string so this doesn't work:
message.data.keys()
*** AttributeError: 'str' object has no attribute 'keys'
Getting All Methods for an Object
The ruby is:
hash = {'foo': 'bar', 'cat': 'blepp'}
(hash.methods - Object.methods).sort
(hash.methods - Object.methods).sort
[
[ 0] :[],
[ 1] :[]=,
[ 2] :all?,
[ 3] :any?,
[ 4] :assert_valid_keys,
[ 5] :assoc,
[ 6] :chain,
[ 7] :chart_json,
[ 8] :chunk,
# a crap ton of methods omitted for clarity
]
# Also and BETTER since it just shows methods on the class; not inherited methods:
plan.public_methods(false)
[
[ 0] active_habits() Plan
[ 1] after_add_for_habits() Plan
[ 2] after_add_for_habits=(val) Plan
[ 3] after_add_for_habits?() Plan
[ 4] after_remove_for_habits() Plan
[ 5] after_remove_for_habits=(val) Plan
[ 6] after_remove_for_habits?() Plan
[ 7] autosave_associated_records_for_habits(*args) Plan
[ 8] before_add_for_habits() Plan
[ 9] before_add_for_habits=(val) Plan
[10] before_add_for_habits?() Plan
[11] before_remove_for_habits() Plan
[12] before_remove_for_habits=(val) Plan
[13] before_remove_for_habits?() Plan
[14] foo(bar) Plan
[15] validate_associated_records_for_habits(*args) Plan
]
The python is:
dir(message)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'data', 'dump', 'event', 'id', 'parse', 'retry', 'sse_line_pattern']
(Pdb) message.sse_line_pattern
re.compile('(?P<name>[^:]*):?( ?(?P<value>.*))?')
# object.__dict__ gets you an object's data while dir(object) gets you an object's methods
See: Me, Lerner, Swlh, Stack Overflow
Getting All Keys in a Hash
The ruby is:
hash = {'foo': 'bar', 'cat': 'blepp'}
2.6.3 :018 > hash.keys
[
[0] :foo,
[1] :cat
]
The python is:
hash = {'foo': 'bar', 'cat': 'blepp'}
hash.keys()
dict_keys(['foo', 'cat'])
JSON Parsing
The ruby is:
json_str = '{"id": 12345, "message": "hello", "array": [1,2,3,4]}'
json = JSON.parse(json_str)
{
"id" => 12345,
"message" => "hello",
"array" => [
[0] 1,
[1] 2,
[2] 3,
[3] 4
]
}
The python is:
import ujson as JSON
json_str = '{"id": 12345, "message": "hello", "array": [1,2,3,4]}'
json = JSON.loads(json_str)
{'id': 12345, 'message': 'hello', 'array': [1, 2, 3, 4]}
json.keys()
dict_keys(['id', 'message', 'array'])
I will confess to dipping into a bit of a Ruby idiom here by importing ujson as JSON (all caps). I find myself constantly making errors in python by calling my json variable json when that's actually an imported class / module. This one change makes things a lot easier (at least for me).
Length / Size of an Object
The ruby is:
plan.name.size
11
The python is:
len(message.data)
1826
Quitting the console
The ruby is:
quit
The python is:
quit()
Redis
The ruby is:
require 'redis'
2.6.3 :005 > redis = Redis.new
#<Redis client v4.1.3 for redis://127.0.0.1:6379/0>
2.6.3 :006 > redis.set("mykey", "hello world")
"OK"
2.6.3 :007 > redis.get("mykey")
"hello world"
The python is:
>>> import redis
>>> r = redis.Redis(host='localhost', port=6379, db=0)
>>> r.set('mykey', 'hello world')
True
>>> r.get('mykey')
'hello world'
String Interpolation
The ruby is:
skill = "beginner"
puts "Scott is a #{skill}"
The python is:
skill = "beginner"
print(f"Scott is a {skill}")
Try / & - Safe Navigation and Python Hashes
A ruby hash returns nil if the key specified is missing:
ENV['foo']
nil
2.3.1 :003 > ENV['RUBY_VERSION']
"ruby-2.3.1"
A python dict raises an exception:
import os
>>> os.environ['RAILS_ENV']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/sjohnson/Sync/fuzzygroup/adl/antihate/antihate_experts/venv/bin/../lib/python3.7/os.py", line 678, in __getitem__
raise KeyError(key) from None
KeyError: 'RAILS_ENV'
>>> os.environ.get('RAILS_ENV')
>>> os.environ.get('RUBY_VERSION')
'ruby-2.3.1'
The ruby way around this would be to use try or the safe navigation operator:
https://stackoverflow.com/questions/46390057/from-ruby-to-python-is-there-an-equivalent-of-try
The Blank for Writing the Next Entry
The ruby is:
The python is: