Nauka języka Python od Rubiego; Różnice i podobieństwa

131

Znam Ruby bardzo dobrze. Uważam, że być może będę musiał teraz nauczyć się Pythona. Dla tych, którzy znają oba, jakie koncepcje są podobne między nimi, a jakie są różne?

Szukam listy podobnej do startera, który napisałem dla Learning Lua for JavaScripters : proste rzeczy, takie jak znaczenie białych znaków i konstrukcje zapętlające; nazwa nilw Pythonie i jakie wartości są uważane za „prawdziwe”; Czy idiomatyczne jest używanie odpowiednika mapi each, czy może mamroczę coś o interpretacjach z listy?

Jeśli otrzymam różnorodne odpowiedzi, z przyjemnością zgrupuję je na wiki społeczności. Albo wszyscy możecie walczyć i odsuwać się od siebie, aby spróbować stworzyć jedną prawdziwą wyczerpującą listę.

Edycja : Żeby było jasne, moim celem jest „właściwy” i idiomatyczny Python. Jeśli istnieje odpowiednik Pythona inject, ale nikt go nie używa, ponieważ istnieje lepszy / inny sposób osiągnięcia wspólnej funkcjonalności polegającej na iterowaniu listy i gromadzeniu wyniku po drodze, chcę wiedzieć, jak robisz rzeczy. Być może zaktualizuję to pytanie o listę typowych celów, jak je osiągasz w Rubim i zapytam, jaki jest odpowiednik w Pythonie.

Phrogz
źródło
1
jedyne, co przeczytałem, to c2.com/cgi/wiki?PythonVsRuby , naprawdę nie lubię siebie i wcięć , ale przyzwyczaiłem się do tego :)
Saif al Harthi
1
Powiązane: stackoverflow.com/questions/1113611/… (nie jestem do końca pewien, czy to duplikat, ponieważ to pytanie dotyczy rzeczy bez odpowiednika).
19
@SilentGhost Zdecydowanie się nie zgadzam. Pytam "Co jest takie samo między językami, a co się różni?" Jak wynika z wielu poniższych odpowiedzi, możliwe są bardzo jasne i pomocne odpowiedzi.
Phrogz
3
@Phrogz: Widzę to i sprawia, że ​​na pytanie nie ma odpowiedzi.
SilentGhost
2
@Phrongz - Aby powtórzyć to, co powiedziałem na temat meta, który opublikowałeś, problem z tym pytaniem polega na tym, że przestrzeń problemowa jest zbyt duża - jest to zbyt duży temat tylko na jedno pytanie. Istnieją tysiące różnic między tymi dwoma językami.
Adam Davis

Odpowiedzi:

153

Oto kilka kluczowych różnic dla mnie:

  1. Ruby ma bloki; Python tego nie robi.

  2. Python ma funkcje; Ruby nie. W Pythonie możesz wziąć dowolną funkcję lub metodę i przekazać ją innej funkcji. W Rubim wszystko jest metodą i metody nie mogą być przekazywane bezpośrednio. Zamiast tego musisz owinąć je w Proc, aby je przekazać.

  3. Ruby i Python obsługują zamknięcia, ale na różne sposoby. W Pythonie możesz zdefiniować funkcję wewnątrz innej funkcji. Funkcja wewnętrzna ma dostęp do odczytu zmiennych z funkcji zewnętrznej, ale nie ma prawa do zapisu. W Rubim domknięcia definiuje się za pomocą bloków. Zamknięcia mają pełny dostęp do odczytu i zapisu do zmiennych z zewnętrznego zakresu.

  4. Python ma listy składane, które są dość ekspresyjne. Na przykład, jeśli masz listę liczb, możesz pisać

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

    aby otrzymać nową listę kwadratów o wszystkich wartościach większych niż 15. W Rubim musiałbyś napisać co następuje:

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

    Kod Ruby nie wydaje się być tak zwarty. Nie jest również tak wydajna, ponieważ najpierw konwertuje tablicę wartości na krótszą tablicę pośrednią zawierającą wartości większe niż 15. Następnie pobiera tablicę pośrednią i generuje tablicę końcową zawierającą kwadraty półproduktów. Tablica pośrednia jest następnie wyrzucana. Tak więc Ruby kończy z 3 tablicami w pamięci podczas obliczeń; Python potrzebuje tylko listy wejściowej i listy wynikowej.

    Python dostarcza również podobne interpretacje map.

  5. Python obsługuje krotki; Ruby tego nie robi. W Rubim musisz używać tablic do symulowania krotek.

  6. Ruby obsługuje instrukcje switch / case; Python tego nie robi.

  7. Ruby obsługuje standardowy expr ? val1 : val2operator trójskładnikowy; Python tego nie robi.

  8. Ruby obsługuje tylko pojedyncze dziedziczenie. Jeśli chcesz naśladować wielokrotne dziedziczenie, możesz zdefiniować moduły i użyć kombinacji, aby przeciągnąć metody modułu do klas. Python obsługuje wielokrotne dziedziczenie zamiast mieszania modułów.

  9. Python obsługuje tylko jednowierszowe funkcje lambda. Bloki Ruby, które są rodzajem / rodzajem funkcji lambda, mogą być dowolnie duże. Z tego powodu kod Ruby jest zwykle napisany w bardziej funkcjonalnym stylu niż kod Pythona. Na przykład, aby zapętlić listę w Rubim, zwykle to robisz

    collection.each do |value|
      ...
    end
    

    Blok działa bardzo podobnie do przekazywanej funkcji collection.each. Gdybyś miał zrobić to samo w Pythonie, musiałbyś zdefiniować nazwaną funkcję wewnętrzną, a następnie przekazać ją do kolekcji każdej metody (jeśli lista obsługuje tę metodę):

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

    To nie układa się zbyt dobrze. Tak więc w Pythonie zwykle używane jest następujące niefunkcjonalne podejście:

    for value in collection:
      ...
    
  10. Korzystanie z zasobów w bezpieczny sposób różni się w obu językach. Tutaj problem polega na tym, że chcesz przydzielić jakiś zasób (otworzyć plik, uzyskać kursor bazy danych itp.), Wykonać na nim dowolną operację, a następnie zamknąć go w bezpieczny sposób, nawet jeśli wystąpi wyjątek.

    W Rubim, ponieważ bloki są tak łatwe w użyciu (patrz # 9), zwykle zakodowałbyś ten wzorzec jako metodę, która pobiera blok do wykonania dowolnej operacji na zasobie.

    W Pythonie przekazywanie funkcji do dowolnej akcji jest trochę bardziej skomplikowane, ponieważ musisz napisać nazwaną funkcję wewnętrzną (patrz # 9). Zamiast tego Python używa withinstrukcji do bezpiecznej obsługi zasobów. Zobacz Jak poprawnie wyczyścić obiekt w języku Python? po więcej szczegółów.

Clint Miller
źródło
2
3. Python 3 nonlocalrozwiązuje ten problem 4. Python daje również wyrażenia generatora (podobne do wyrażeń listowych, ale nie obliczaj niczego, dopóki nie zostaniesz o to poproszony - pomyśl o wyrażeniach listowych jak o wyrażeniach generatora dostarczanych do list(które przyjmuje iterowalne i zwraca listę zawierającą iterowalne wyniki) - w niektórych przypadkach może to zaoszczędzić wiele wysiłku).
25
7. Tak, to prawda. val1 if expr else val2. 8. Chociaż widzę, że jest używany głównie do ulepszeń w stylu miksowania.
2
@ClintMiller Whoa, bez przełącznika / obudowy? Jaki jest więc sugerowany sposób osiągnięcia podobnej funkcjonalności w Pythonie? if / else / if?
Phrogz
15
Twój rubinowy przykład w # 4 nie jest idiomatyczny. Byłoby bardziej rubinowe (i czytelne) do pisania values.map{|v| v*v if v > 15}.compact. IMHO, to jest jeszcze bardziej wyraziste (iz pewnością jaśniejsze) niż twój przykład w Pythonie.
sml
10
Oprócz powyższego, za pomocą! wersja funkcji kompaktowej unika kopię tablicy: values.map{|v| v*v if v > 15}.compact!. Oznacza to, że w pamięci istnieje tylko lista wejść i lista wynikowa. Zobacz nr 4 tutaj: igvita.com/2008/07/08/6-optimization-tips-for-ruby-mri
sml
27

Właśnie spędziłem kilka miesięcy na nauce Pythona po 6 latach Rubiego. Naprawdę nie było dobrego porównania dla tych dwóch języków, więc zdecydowałem się zestresować i sam napisać jeden. Teraz jest zainteresowana głównie z programowania funkcyjnego, ale skoro wspomniałeś Ruby injectmetody, zgaduję, że jesteśmy na tej samej fali.

Mam nadzieję, że to pomoże: „brzydota” Pythona

Kilka punktów, które poprowadzą Cię we właściwym kierunku:

  • Cała funkcjonalność programowania, której używasz w Ruby, jest w Pythonie i jest jeszcze łatwiejsza. Na przykład możesz mapować funkcje dokładnie tak, jak się spodziewasz:

    def f(x):
        return x + 1
    
    map(f, [1, 2, 3]) # => [2, 3, 4]
    
  • Python nie ma metody, która działa jak each. Ponieważ używasz tylko eachdo efektów ubocznych, odpowiednikiem w Pythonie jest pętla for:

    for n in [1, 2, 3]:
        print n
    
  • Listy składane są świetne, gdy a) masz do czynienia z funkcjami i zbiorami obiektów razem oraz b) gdy potrzebujesz iterować przy użyciu wielu indeksów. Na przykład, aby znaleźć wszystkie palindromy w ciągu (zakładając, że masz funkcję, p()która zwraca prawdę dla palindromów), wszystko, czego potrzebujesz, to pojedyncze wyrażenie listy:

    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.
źródło
3
Wzdycham, przeczytałem ten post i potwierdza moje podejrzenia: niewiele osób rozumie rolę i użyteczność specjalnych metod w Pythonie. Są niezwykle przydatne i ustandaryzowane i podkreślone w ten sposób, aby uniknąć konfliktów nazw z wbudowanymi, które często implementują. Nikt, kto faktycznie zna Pythona, nie próbuje zniechęcać do ich używania.
Rafe Kettler
5
Wydaje się, że nie rozumiesz, jak działają metody. Metoda to w istocie funkcja, której pierwszym argumentem jest instancja klasy, do której należy metoda. Kiedy piszesz Class.method, metoda jest „niezwiązana”, a pierwszy argument powinien być Classinstancją; kiedy piszesz object.method, metoda jest „związana” z objectinstancją Class. Pozwala to wybrać, czy używać map (itp.) Do wywoływania metody na innej instancji za każdym razem (przekazywać niezwiązaną metodę), czy też pozostawić instancję stałą i za każdym razem przekazywać inny drugi argument. Obie są przydatne.
LaC
2
Masz rację, nie rozumiałem, jak działały. Odkąd opublikowałem artykuł, nabrałem tego sensu. Dzięki!
David J.,
10
[s[x:y] for x in range(l) for y in range(x,l+1) if p(s[x:y])]- ta linia pokazuje, jak trudny do czytania jest Python. Kiedy czytasz kod Ruby, poruszasz oczami od lewej do prawej, bez powrotu. Ale aby czytać kod w Pythonie, musisz iść w lewo-prawo-lewo-prawo-lewo-prawo ... i nawiasy, nawiasy, nawiasy, nawiasy ... Również w Pythonie często potrzebujesz mieszania metod i funkcji. To szaleństwo: E(C(A.B()).D())zamiast Ruby'sA.B.C.D.E
Nakilon
2
@Nakilon Dlatego powinieneś używać zagnieżdżonych list składanych tylko dla naprawdę prostych przypadków, a nie jak powyżej. „Sprytne” mogłoby być napisanie jednowierszowego tekstu, który znajdzie wszystkie palindromy w łańcuchu, ale najlepiej jest go zarezerwować dla kodu golfowego. Aby uzyskać prawdziwy kod, który ktoś inny będzie musiał później przeczytać, po prostu napiszesz kilka funkcji. Więc tak, ta linia jest trudna do odczytania, ale to wina programisty, a nie języka.
Cam Jackson
10

Moja sugestia: nie próbuj uczyć się różnic. Dowiedz się, jak podejść do problemu w Pythonie. Tak jak istnieje podejście Ruby do każdego problemu (które działa bardzo dobrze, biorąc pod uwagę ograniczenia i mocne strony języka), istnieje podejście Pythona do problemu. są różne. Aby jak najlepiej wykorzystać każdy język, naprawdę powinieneś nauczyć się samego języka, a nie tylko „tłumaczenia” z jednego na drugi.

Mając to na uwadze, różnica pomoże Ci szybciej dostosować się i wprowadzić jedną modyfikację w programie w języku Python. I to jest dobre na początek, żeby zacząć pisać. Ale spróbuj dowiedzieć się z innych projektów, dlaczego stoją za architekturą i decyzjami projektowymi, a nie jak kryje się za semantyką języka ...

ircmaxell
źródło
7
Doceniam twoją sugestię. Całkowicie zgadzam się z sentencją (którą interpretuję jako „Naucz się programować idiomatyczny Python”) . Dokładnie to próbuję zrobić. Nie pytam „Jaka jest nazwa eachmetody Rubiego w Pythonie ?” Pytam: „W jaki sposób rzeczy robione poprawnie w Pythonie różnią się od Ruby, a gdzie są wykonywane poprawnie tak samo?” Jeśli Python falsejest w rzeczywistości False, tak samo ważne jest, aby wiedzieć, gdzie i kiedy powinienem robić rzeczy w Rubyes sposób, a gdzie i kiedy nie.
Phrogz,
2
@Phrogz: To sprawiedliwe. Sposób, w jaki zinterpretowałem twoje pytanie, brzmiał: Zróbmy listę różnic, abyśmy mogli po prostu zmienić język, w którym programujemy . Ale to uczciwe pytanie. Chyba po prostu źle zinterpretowałem to, o co prosiłeś. Zostawię to tutaj w celach informacyjnych, ale ciekawie będzie zobaczyć, co jeszcze się pojawi ...
ircmaxell
Uczę się Pythona i Ruby w tym samym czasie, aw aplikacji webowej widzę więcej podobieństw niż różnic.
WesternGun
8

Znam trochę Rubiego, ale oto kilka punktów na temat rzeczy, o których wspomniałeś:

  • nil, wartością wskazującą na brak wartości będzie None(zwróć uwagę, że sprawdzasz ją tak, jak x is Nonelub x is not None, nie za pomocą ==- lub przez wymuszenie na wartości logiczne, patrz następny punkt).
  • NoneNumery zero-esque ( 0, 0.0, 0j(liczba zespolona)) i pustych zbiorów ( [], {}, set(), pusty łańcuch "", etc.) są uważane falsy, wszystko inne jest uważany truthy.
  • W przypadku efektów ubocznych ( for-) jawnie zapętla się. Aby wygenerować nową grupę rzeczy bez skutków ubocznych, użyj wyrażeń listowych (lub ich krewnych - wyrażeń generatora dla leniwych jednorazowych iteratorów, wyrażeń dykt / set dla wspomnianych kolekcji).

Odnośnie pętli: masz for, który działa na iterowalnym (! Bez liczenia) i whilektóry robi to, czego można się spodziewać. Fromer jest znacznie potężniejszy dzięki szerokiemu wsparciu dla iteratorów. Nie tylko prawie wszystko, co może być iteratorem zamiast listy, jest iteratorem (przynajmniej w Pythonie 3 - w Pythonie 2 masz jedno i drugie, a domyślną jest niestety lista). Istnieje wiele narzędzi do pracy z iteratorami - zipiteruje dowolną liczbę iteracji równolegle, enumeratedaje (index, item)(na dowolnych iterowalnych, nie tylko na listach), a nawet cięcie abritarnych (prawdopodobnie dużych lub nieskończonych) iteracji! Zauważyłem, że znacznie upraszcza to wiele zadań pętli. Nie trzeba dodawać, że dobrze integrują się ze składanymi listami, wyrażeniami generatora itp.


źródło
2
Wyrażenia generatora są fajne. Dają Pythonowi trochę leniwych możliwości oceny języków takich jak Haskell.
Clint Miller
@Clint: Tak. Pełne generatory są jeszcze bardziej wydajne (chociaż nie są potrzebne w prostych przypadkach, których jest większość).
Dlaczego sprawdzasz z x is Nonelub x is not None? Zawsze sprawdzam z x == Nonei x != None.
John
@John: Jeśli xdefiniuje się __eq__w głupi sposób, może dać fałszywy alarm . Jeśli __eq__nie zostanie odpowiednio zaprogramowany, może się zawiesić (np. AttributeError) Po podaniu pewnych wartości (tj None.). Wręcz przeciwnie, isnie można go przesłonić - zawsze porównuje tożsamość obiektu, co jest właściwym (najbardziej niezawodnym, najprostszym i najczystszym) sposobem sprawdzenia singletona.
1
@Jan. „x to None” to absolutnie idiomatyczny sposób na zrobienie tego. python.net/~goodger/projects/pycon/2007/idiomatic/handout.html
tokland
6

W Rubim zmienne instancji i metody nie są ze sobą powiązane, z wyjątkiem sytuacji, gdy jawnie powiązujesz je z attr_accessor lub czymś w tym rodzaju.

W Pythonie metody są po prostu specjalną klasą atrybutów: taką, która jest wykonywalna.

Na przykład:

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

Ta różnica ma wiele implikacji, jak na przykład odwołanie się do fx odnosi się do obiektu metody, a nie do jej wywoływania. Jak widać, fx jest domyślnie publiczne, podczas gdy w Rubim zmienne instancji są domyślnie prywatne.

Paul Prescod
źródło
2
Właściwie powiedziałbym to jeszcze wyraźniej: w Pythonie metody są tylko szczególnym rodzajem atrybutów, podczas gdy w Rubim atrybuty są tylko szczególnym rodzajem metody.
Wynika