Apprendre Python à partir de Ruby; Différences et similitudes

131

Je connais très bien Ruby. Je pense que j'ai peut-être besoin d'apprendre Python actuellement. Pour ceux qui connaissent les deux, quels concepts sont similaires entre les deux et qu'est-ce qui est différent?

Je recherche une liste similaire à une introduction que j'ai écrite pour Learning Lua pour les JavaScripters : des choses simples comme la signification des espaces blancs et les constructions en boucle; le nom de nilen Python, et quelles valeurs sont considérées comme "véridiques"; est-il idiomatique d'utiliser l'équivalent de mapet each, ou marmonne-t-il quelque chose à propos de la liste des compréhensions marmonnant la norme?

Si j'obtiens une bonne variété de réponses, je suis heureux de les regrouper dans un wiki communautaire. Ou bien vous pouvez tous vous battre et vous bercer les uns des autres pour essayer de créer la seule véritable liste complète.

Edit : Pour être clair, mon objectif est Python "approprié" et idiomatique. S'il existe un équivalent Python de inject, mais que personne ne l'utilise car il existe un moyen meilleur / différent d'obtenir la fonctionnalité commune d'itération d'une liste et d'accumuler un résultat en cours de route, je veux savoir comment vous faites les choses. Je vais peut-être mettre à jour cette question avec une liste d'objectifs communs, comment les atteindre en Ruby, et demander quel est l'équivalent en Python.

Phrogz
la source
1
la seule chose que j'ai lu était c2.com/cgi/wiki?PythonVsRuby , je n'aime vraiment pas moi-même et les indentions mais je m'y suis habitué :)
Saif al Harthi
1
En relation: stackoverflow.com/questions/1113611/… (je ne suis pas sûr qu'il s'agisse d'un doublon, car cette question demande des choses sans équivalent).
19
@SilentGhost Je ne suis pas du tout d'accord. Je demande "Qu'est-ce qui est pareil entre les langues et qu'est-ce qui est différent?" Comme le montrent de nombreuses réponses ci-dessous, des réponses très claires et utiles sont possibles à cet égard.
Phrogz
3
@Phrogz: Je vois cela et rend la question sans réponse.
SilentGhost
2
@Phrongz - Pour faire écho à ce que j'ai dit sur le méta-sujet que vous avez posté, le problème avec cette question est que l'espace du problème est trop grand - c'est un sujet trop gros pour une seule question. Il existe des milliers de différences entre les deux langues.
Adam Davis

Réponses:

153

Voici quelques différences clés pour moi:

  1. Ruby a des blocs; Python ne le fait pas.

  2. Python a des fonctions; Ruby ne le fait pas. En Python, vous pouvez prendre n'importe quelle fonction ou méthode et la transmettre à une autre fonction. Dans Ruby, tout est une méthode et les méthodes ne peuvent pas être transmises directement. Au lieu de cela, vous devez les envelopper dans Proc pour les transmettre.

  3. Ruby et Python prennent tous deux en charge les fermetures, mais de manière différente. En Python, vous pouvez définir une fonction dans une autre fonction. La fonction interne a un accès en lecture aux variables de la fonction externe, mais pas un accès en écriture. Dans Ruby, vous définissez les fermetures à l'aide de blocs. Les fermetures ont un accès complet en lecture et en écriture aux variables de la portée externe.

  4. Python a des compréhensions de liste, qui sont assez expressives. Par exemple, si vous avez une liste de nombres, vous pouvez écrire

    [x*x for x in values if x > 15]

    pour obtenir une nouvelle liste des carrés de toutes les valeurs supérieures à 15. Dans Ruby, vous devez écrire ce qui suit:

    values.select {|v| v > 15}.map {|v| v * v}

    Le code Ruby n'est pas aussi compact. Il n'est pas non plus aussi efficace car il convertit d'abord le tableau de valeurs en un tableau intermédiaire plus court contenant les valeurs supérieures à 15. Ensuite, il prend le tableau intermédiaire et génère un tableau final contenant les carrés des intermédiaires. Le tableau intermédiaire est ensuite jeté. Ainsi, Ruby se retrouve avec 3 tableaux en mémoire pendant le calcul; Python n'a besoin que de la liste d'entrée et de la liste résultante.

    Python fournit également des compréhensions de carte similaires.

  5. Python prend en charge les tuples; Ruby ne le fait pas. Dans Ruby, vous devez utiliser des tableaux pour simuler des tuples.

  6. Ruby prend en charge les instructions switch / case; Python ne le fait pas.

  7. Ruby prend en charge l' expr ? val1 : val2opérateur ternaire standard ; Python ne le fait pas.

  8. Ruby ne prend en charge qu'un seul héritage. Si vous avez besoin d'imiter l'héritage multiple, vous pouvez définir des modules et utiliser des mix-ins pour extraire les méthodes de module en classes. Python prend en charge l'héritage multiple plutôt que les mix-ins de modules.

  9. Python prend en charge uniquement les fonctions lambda sur une seule ligne. Les blocs Ruby, qui sont une sorte de / sorte de fonctions lambda, peuvent être arbitrairement grands. Pour cette raison, le code Ruby est généralement écrit dans un style plus fonctionnel que le code Python. Par exemple, pour parcourir une liste dans Ruby, vous faites généralement

    collection.each do |value|
      ...
    end

    Le bloc fonctionne très bien comme une fonction transmise collection.each. Si vous deviez faire la même chose en Python, vous devrez définir une fonction interne nommée, puis la transmettre à la collection chaque méthode (si la liste prend en charge cette méthode):

    def some_operation(value):
      ...
    
    collection.each(some_operation)

    Cela ne va pas très bien. Ainsi, l'approche non fonctionnelle suivante serait généralement utilisée en Python:

    for value in collection:
      ...
  10. Utiliser les ressources de manière sûre est assez différent entre les deux langues. Ici, le problème est que vous souhaitez allouer une ressource (ouvrir un fichier, obtenir un curseur de base de données, etc.), effectuer une opération arbitraire dessus, puis la fermer de manière sûre même si une exception se produit.

    Dans Ruby, étant donné que les blocs sont si faciles à utiliser (voir # 9), vous coderiez généralement ce modèle comme une méthode qui prend un bloc pour l'opération arbitraire à effectuer sur la ressource.

    En Python, passer une fonction pour l'action arbitraire est un peu plus maladroit puisque vous devez écrire une fonction interne nommée (voir # 9). Au lieu de cela, Python utilise une withinstruction pour une gestion sûre des ressources. Consultez Comment nettoyer correctement un objet Python? pour plus de détails.

Clint Miller
la source
2
3. Python 3 nonlocalcorrige ce problème 4. Python vous donne également des expressions génératrices (similaires aux compréhensions de liste, mais ne calculez rien tant qu'on ne vous le demande pas - pensez aux compréhensions de listes comme des expressions génératrices alimentées list(qui prend un itérable et renvoie une liste contenant tout l'itérable produit) - cela peut économiser beaucoup d'efforts dans certains cas).
25
7. Oui, c'est vrai. val1 if expr else val2. 8. Bien que je le vois principalement utilisé pour l'augmentation de style mixin.
2
@ClintMiller Whoa, pas de commutateur / boîtier? Alors, quelle est la manière suggérée d'obtenir des fonctionnalités similaires en Python? si / autre / si?
Phrogz
15
Votre exemple de rubis dans # 4 n'est pas idiomatique. Ce serait plus rubis (et lisible) à écrire values.map{|v| v*v if v > 15}.compact. IMHO, c'est encore plus expressif (et certainement plus clair) que votre exemple python.
sml
10
En plus de ce qui précède, utilisez le! version de la fonction compacte évite une copie du tableau: values.map{|v| v*v if v > 15}.compact!. Cela signifie que seules la liste d'entrée et la liste résultante existent en mémoire. Voir le n ° 4 ici: igvita.com/2008/07/08/6-optimization-tips-for-ruby-mri
sml
27

Je viens de passer quelques mois à apprendre Python après 6 ans de Ruby. Il n'y avait vraiment pas de comparaison formidable pour les deux langues, alors j'ai décidé de m'en occuper et d'en écrire une moi-même. Maintenant, il s'agit principalement de programmation fonctionnelle, mais puisque vous mentionnez la injectméthode de Ruby , je suppose que nous sommes sur la même longueur d'onde.

J'espère que cela aide: La `` laideur '' de Python

Quelques points qui vous permettront d'aller dans la bonne direction:

  • Tous les avantages de la programmation fonctionnelle que vous utilisez dans Ruby sont en Python, et c'est encore plus facile. Par exemple, vous pouvez mapper des fonctions exactement comme vous vous y attendez:

    def f(x):
        return x + 1
    
    map(f, [1, 2, 3]) # => [2, 3, 4]
  • Python n'a pas de méthode qui agit comme each. Puisque vous n'utilisez que eachpour les effets secondaires, l'équivalent en Python est la boucle for:

    for n in [1, 2, 3]:
        print n
  • Les compréhensions de listes sont excellentes lorsque a) vous devez gérer ensemble des fonctions et des collections d'objets et b) lorsque vous avez besoin d'itérer en utilisant plusieurs index. Par exemple, pour trouver tous les palindromes dans une chaîne (en supposant que vous ayez une fonction p()qui renvoie true pour les palindromes), tout ce dont vous avez besoin est une seule compréhension de liste:

    s = 'string-with-palindromes-like-abbalabba'
    l = len(s)
    [s[x:y] for x in range(l) for y in range(x,l+1) if p(s[x:y])]
David J.
la source
3
Soupir, j'ai lu cet article et cela confirme mes soupçons: peu de gens comprennent le rôle et l'utilité des méthodes spéciales en Python. Ils sont incroyablement utiles et standardisés, et ils sont soulignés comme tel pour éviter les conflits de noms avec les fonctions intégrées qu'ils implémentent souvent. Personne qui connaît réellement Python n'essaie de décourager leur utilisation.
Rafe Kettler le
5
Vous ne semblez pas comprendre comment fonctionnent les méthodes. Une méthode est, essentiellement, une fonction dont le premier argument est une instance de la classe à laquelle appartient la méthode. Lorsque vous écrivez Class.method, la méthode est "indépendante" et le premier argument doit être une Classinstance; lorsque vous écrivez object.method, la méthode est "liée" à l' objectinstance de Class. Cela vous permet de choisir d'utiliser map (etc.) pour appeler la méthode sur une instance de différence à chaque fois (passer une méthode indépendante), ou pour maintenir l'instance fixe et passer un deuxième argument différent à chaque fois. Les deux sont utiles.
LaC
2
Vous avez raison, je n'ai pas compris comment ils fonctionnaient. Depuis la publication de l'article, j'en ai une meilleure idée. Merci!
David J.
10
[s[x:y] for x in range(l) for y in range(x,l+1) if p(s[x:y])]- cette ligne montre à quel point Python est difficile à lire. Lorsque vous lisez du code Ruby, vous déplacez vos yeux de gauche à droite, sans retour. Mais pour lire le code Python, vous devez aller gauche-droite-gauche-droite-gauche-droite ... et parenthèses, parenthèses, parenthèses, parenthèses ... Aussi en Python, vous avez souvent besoin de mélanger des méthodes et des fonctions. C'est de la folie: E(C(A.B()).D())au lieu de Ruby'sA.B.C.D.E
Nakilon
2
@Nakilon C'est pourquoi vous ne devriez utiliser les compréhensions de listes imbriquées que pour des cas vraiment simples, et pas comme ci-dessus. Il peut être «intelligent» d'écrire une ligne unique qui trouve tous les palindromes dans une chaîne, mais il est préférable de la réserver au golf codé. Pour le vrai code que quelqu'un d'autre doit lire plus tard, vous écririez simplement une fonction de quelques lignes. Alors oui, cette ligne est difficile à lire, mais c'est la faute du programmeur, pas celle du langage.
Cam Jackson
10

Ma suggestion: n'essayez pas d'apprendre les différences. Apprenez à aborder le problème en Python. Tout comme il existe une approche Ruby pour chaque problème (qui fonctionne très bien compte tenu des limites et des forces du langage), il existe une approche Python du problème. ils sont tous les deux différents. Pour tirer le meilleur parti de chaque langue, vous devez vraiment apprendre la langue elle-même, et pas seulement la «traduction» de l'une à l'autre.

Maintenant, cela dit, la différence vous aidera à vous adapter plus rapidement et à apporter une modification à un programme Python. Et c'est bien pour commencer à écrire. Mais essayez d'apprendre des autres projets le pourquoi derrière les décisions d'architecture et de conception plutôt que le comment derrière la sémantique du langage ...

ircmaxell
la source
7
J'apprécie votre suggestion. Je suis entièrement d'accord avec le sentiment (que j'interprète comme "Apprenez à programmer Python idiomatique") . C'est précisément ce que j'essaye de faire. Je ne demande pas "Quel est le nom Python de la eachméthode Ruby ?" Je demande "En quoi les choses sont-elles faites correctement en Python différentes de Ruby et où sont-elles faites correctement de la même manière?" Si Python l' falseest réellement False, c'est aussi important de savoir que où et quand je devrais faire les choses de manière Rubyesque, et où et quand je ne devrais pas.
Phrogz
2
@Phrogz: C'est juste. La façon dont j'ai interprété votre question était la suivante: faisons une liste des différences afin que nous puissions simplement changer le langage dans lequel nous programmons . Mais c'est une bonne question. Je suppose que j'ai mal interprété ce que vous demandiez. Je vais laisser ceci ici pour référence, mais il sera intéressant de voir ce qui se passe d'autre ...
ircmaxell
J'apprends python et ruby ​​en même temps, et dans le développement d'applications Web, je vois plus de similitudes que de différences.
WesternGun
8

Je connais le petit Ruby, mais voici quelques points sur les choses que vous avez mentionnées:

  • nil, la valeur indiquant l'absence de valeur, serait None(notez que vous la vérifiez comme x is Noneou x is not None, pas avec ==- ou par coercition en booléen, voir le point suivant).
  • None, Le nombre zéro-esque ( 0, 0.0, 0j(nombre complexe)) et des collections vides ( [], {}, set(), la chaîne vide"" , etc.) sont considérés comme falsy, tout le reste est considéré comme truthy.
  • Pour les effets secondaires, forboucle ( -) explicitement. Pour générer un nouveau tas de trucs sans effets secondaires, utilisez les compréhensions de liste (ou leurs parents - expressions génératrices pour les itérateurs ponctuels paresseux, compréhensions dict / set pour lesdites collections).

Concernant le bouclage: Vous avez for, qui opère sur un itérable (! Pas de comptage), et while, qui fait ce que vous attendez. Le fromer est beaucoup plus puissant, grâce à la prise en charge étendue des itérateurs. Non seulement presque tout ce qui peut être un itérateur au lieu d'une liste est un itérateur (au moins dans Python 3 - dans Python 2, vous avez les deux et la valeur par défaut est une liste, malheureusement). Il existe de nombreux outils pour travailler avec des itérateurs - zipitère n'importe quel nombre d'itérables en parallèle, enumeratevous donne (index, item)(sur n'importe quel itérable, pas seulement sur des listes), même découpant des itérables abritaires (éventuellement grands ou infinis)! J'ai trouvé que cela simplifiait beaucoup de tâches en boucle. Inutile de dire qu'ils s'intègrent très bien avec les compréhensions de liste, les expressions génératrices, etc.


la source
2
Les expressions du générateur sont cool. Ils donnent à Python un peu des capacités d'évaluation paresseuses de langages comme Haskell.
Clint Miller du
@Clint: Oui. Et les générateurs complets sont encore plus capables (bien que non nécessaires pour les cas simples, qui se trouvent être la majorité).
Pourquoi vérifiez-vous avec x is Noneou x is not None? Je vérifie toujours avec x == Noneet x != None.
John
@John: Si xdéfinit __eq__de manière idiote, cela pourrait donner un faux positif. Si le __eq__n'est pas programmé assez soigneusement, il pourrait planter (par exemple AttributeError) lorsqu'on lui donne certaines valeurs (c'est-à-dire None). Au contraire, isne peut pas être remplacé - il compare toujours l'identité de l'objet, ce qui est la bonne façon (la plus robuste, la plus simple et la plus propre) de rechercher un singleton.
1
@John. «x is None» est la manière absolument idiomatique de le faire. python.net/~goodger/projects/pycon/2007/idiomatic/handout.html
tokland
6

Dans Ruby, les variables d'instance et les méthodes sont totalement indépendantes, sauf lorsque vous les associez explicitement à attr_accessor ou quelque chose du genre.

En Python, les méthodes ne sont qu'une classe spéciale d'attributs: une classe exécutable.

Donc par exemple:

>>> class foo:
...     x = 5
...     def y(): pass
... 
>>> f = foo()
>>> type(f.x)
<type 'int'>
>>> type(f.y)
<type 'instancemethod'>

Cette différence a de nombreuses implications, comme par exemple le fait que se référer à fx fait référence à l'objet méthode, plutôt que de l'appeler. De plus, comme vous pouvez le voir, fx est public par défaut, alors que dans Ruby, les variables d'instance sont privées par défaut.

Paul Prescod
la source
2
En fait, je le dirais encore plus clairement: en Python, les méthodes ne sont qu'un type particulier d'attribut, alors qu'en Ruby, les attributs ne sont qu'un type particulier de méthode. Certaines caractéristiques de contraste importantes entre les deux langages en
découlent