Disclamer: Esta comparación viene de mi experiencia personal y es subjetiva.
He tenido la oportunidad de trabajar con ambos lenguajes, Ruby y Python, y aunque no puedo decir que un lenguaje sea mejor que el otro, sin duda hay características en cada lenguaje que lo hacen destacar. En este articulo les hablo acerca de los features que extraño de Ruby y que me gustaría ver en Python.
En Ruby puedes definir tus dependencias de desarrollo, de pruebas y de producción en un solo archivo, el Gemfile. Dentro de este puedes además definir cosas como la fuente de las gemas (dependencias) o agregar condiciones para instalar dichas gemas y cuando las instalas se genera un archivo gemfile.lock con las versiones exactas que todos en el proyecto pueden usar para que todos tengan la version exacta actual de las dependencias.
Ejemplo de gemfile:
source 'https://rubygems.org'unless ENV['QUICK']
gemspec
gem 'rake'
gem 'rack', git:'https://github.com/rack/rack.git'
gem 'rack-test', '>= 0.6.2'
gem "minitest", "~> 5.0"
gem 'yard'
gem "rack-protection", path:"rack-protection"
gem "sinatra-contrib", path:"sinatra-contrib"
gem 'wirble', :group => :development
gem 'debugger', :group => [:development, :test]
...
En Python no hay equivalente al gemfile, solo tenemos un único archivo requements.txt donde se especifican las dependencias. Si queremos dependencias por ambiente, debemos generar varios archivos. Si especificas un rango de versiones en vez de una version exacta, no hay garantías de que dos personas instalando un mismo proyecto tengan las mismas versiones (por eso la buena practica es siempre definir las dependencias exactas).
Ejemplo de un archivo requirements.txt:
Flask==0.8
Jinja2==2.6
Werkzeug==0.8.3
certifi==0.0.8
chardet==1.0.1
distribute==0.6.24
gunicorn==0.14.2
requests>=0.11.1
Hay varios proyectos que buscan mejorar esto, pero nada realmente estándar.
Saber cuáles son y dónde están definidas las dependencias de Ruby es muy fácil, solo hay que ver el gemfile y el gemfile.lock. En un proyecto Python hay que revisar un poco más: usualmente son 4 archivos, un commons.txt, un production.txt, un test.txt, y el requirements.txt. Es más complicado saber qué dependencias son por cada ambiente.
Para acceder a las variables del objeto o de clase se debe hacer a través de una variable que se define comúnmente como self
(para variables de clase usan cls o klass
), lo que trae como consecuencia tener self
por todas partes y que todos los métodos tengan siempre el parámetro de función self
.
def__init__(self, a, b):self.a = a
self.b = b
self.minimum = min(self.minimum, self.maximum)
self.maximum = max(self.minimum, self.maximum)
Ruby no tiene ese problema porque sigue una convención simple de @<variable> para las variables de instancia y @@<variable> para las variables de clase.
def initialize(a, b)
@a = a
@b = b
@minimum = [min, max].min
@minimum = [min, max].maxend
Actualizar Python 2.7 a 3.x es increíblemente doloroso, a Instagram le costo varios meses en migrar a Python 3, en Ruby:
rvmupgrade 2.1.1 2.1.2
En realidad no es tan fácil si tienes Rails, debes también instalar dependencias compatibles, pero el cambio entre versiones jamás a sido tan brusco.
El zen de Python dice entre otras cosas
There should be one – and preferably only one – obvious way to do it
Sin embargo para formateo de strings en Python no se cumple, hay muchas formas de formatear un string.
name = "guido"
print("hello %s" % name ) # hello guido
print("hello {}".format(name)) # hello guido
print("hello {name}".format(name=name)) # hello guido
print(f'hello {name}') # hello guidoclassPerson:def__init__(self, name):
self.name = name
def__format__(self, format):if (format == 'hello'):
return"hello " + self.name # :')return'Hello everyone'
guido = Person(name='guido')
print('{:hello}'.format(guido)) # hello guido
El problema de esto es: “¿Y cuál es la mejor? ¿Cada vez que salga una nueva forma debo refactorizar todo?”
En Ruby:
name = "matz "
puts "Hello, #{name}!"# hello matz
Bonus: En Ruby puedo agregar expresiones en el string y son evaluadas, incluso se puede definir una clase.
puts "Hello world #{1 + 1}!"# hello world 2
Teniendo esas capacidades no es necesario crear más formas de formatear para permitir más features.
En Python no existen los métodos/atributos privados. Aun así la comunidad de Python (basado en lo que he visto del código de varias librerías) ha sentido la necesidad de tenerlos. Han optado por hacer uso del name mangling que ocurre cuando se definen variables/métodos con underscore para emular variables/métodos privados.
classPersona:def__init__(self, name, private_data):
self.name = name
self.__private_data = private_data
def__private(self):#some operationreturnTruedefpublic_operation(self):
self.__private()
# more code returnTrue
guido = Persona("guido", {'security_number': 123456})
guido.public_operation() # True
guido.__private() # AttributeError: 'Persona' object has no attribute '__private'
guido.__private_data # AttributeError: 'Persona' object has no attribute '__private_data'# Es complicado, pero aun se pueden acceder
guido._Persona__private() # True
guido._Persona__private_data # {'security_number': 123456}
Nota 1: en Ruby también se pueden acceder a métodos privados usando reflexion.
Nota de la nota: en Ruby en realidad los atributos no son privados, son protegidos.
La version de ruby de este código sería:
classPersonattr_accessor:namedefinitialize(name, private_data)
@name = name
@private_data = private_data
enddefpublic_operation#some codeend
private
defprivate_operation#some codeend
La verdad es que esto hace que la legibilidad se pierda: si solo Python tuviera identificadores privados, podríamos evitar la sintaxis de los métodos mágicos y métodos/atributos privados.
Ejemplo: el código de mis sueños
classPersona:definit(name, private_data): @name = name @private_data = private_datadefpublic_operation():
private()
# more code returnTrue
private
# methods below are privatedefprivate():#some operationreturnTrue
Espero que les halla gustado. Si no están de acuerdo o creen que falto mencionar algo dejen sus comentarios y sigamos la conversación.