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 nil
w Pythonie i jakie wartości są uważane za „prawdziwe”; Czy idiomatyczne jest używanie odpowiednika map
i 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.
Odpowiedzi:
Oto kilka kluczowych różnic dla mnie:
Ruby ma bloki; Python tego nie robi.
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ć.
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.
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.
Python obsługuje krotki; Ruby tego nie robi. W Rubim musisz używać tablic do symulowania krotek.
Ruby obsługuje instrukcje switch / case; Python tego nie robi.
Ruby obsługuje standardowyexpr ? val1 : val2
operator trójskładnikowy; Python tego nie robi.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.
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: ...
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
with
instrukcji do bezpiecznej obsługi zasobów. Zobacz Jak poprawnie wyczyścić obiekt w języku Python? po więcej szczegółów.źródło
nonlocal
rozwią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 dolist
(które przyjmuje iterowalne i zwraca listę zawierającą iterowalne wyniki) - w niektórych przypadkach może to zaoszczędzić wiele wysiłku).val1 if expr else val2
. 8. Chociaż widzę, że jest używany głównie do ulepszeń w stylu miksowania.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.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-mriWł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
inject
metody, 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 tylkoeach
do 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])]
źródło
Class.method
, metoda jest „niezwiązana”, a pierwszy argument powinien byćClass
instancją; kiedy piszeszobject.method
, metoda jest „związana” zobject
instancją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.[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
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 ...
źródło
each
metody 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 Pythonfalse
jest w rzeczywistościFalse
, tak samo ważne jest, aby wiedzieć, gdzie i kiedy powinienem robić rzeczy w Rubyes sposób, a gdzie i kiedy nie.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ędzieNone
(zwróć uwagę, że sprawdzasz ją tak, jakx is None
lubx is not None
, nie za pomocą==
- lub przez wymuszenie na wartości logiczne, patrz następny punkt).None
Numery 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.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) iwhile
któ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 -zip
iteruje dowolną liczbę iteracji równolegle,enumerate
daje(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
x is None
lubx is not None
? Zawsze sprawdzam zx == None
ix != None
.x
definiuje 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 (tjNone
.). Wręcz przeciwnie,is
nie można go przesłonić - zawsze porównuje tożsamość obiektu, co jest właściwym (najbardziej niezawodnym, najprostszym i najczystszym) sposobem sprawdzenia singletona.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.
źródło