Python'da, yöntem yerine bir işlevi ne zaman kullanmalıyım?


118

Zen of Python, işleri yapmanın yalnızca bir yolu olması gerektiğini belirtir - ancak sıklıkla bir işlevi ne zaman kullanacağıma ve bir yöntemi ne zaman kullanacağıma karar verme sorunuyla karşılaşıyorum.

Önemsiz bir örneği ele alalım - bir ChessBoard nesnesi. Diyelim ki tahtadaki tüm yasal King hamlelerini almak için bir yola ihtiyacımız var. ChessBoard.get_king_moves () yazıyor muyuz yoksa get_king_moves (chess_board) mı?

İşte baktığım bazı ilgili sorular:

Aldığım cevaplar büyük ölçüde yetersizdi:

Python neden bazı işlevler için yöntemler kullanır (ör. List.index ()) ama diğerleri için işlev görür (ör. Len (liste))?

Bunun başlıca nedeni tarih. Fonksiyonlar, bir grup tip için jenerik olan ve hiç metodu olmayan nesneler için bile çalışması amaçlanan işlemler için kullanıldı (örn. Tuple). Python'un (map (), apply () ve diğerleri) işlevsel özelliklerini kullandığınızda, amorf bir nesne koleksiyonuna kolayca uygulanabilecek bir işleve sahip olmak da uygundur.

Aslında, yerleşik bir işlev olarak len (), max (), min () uygulamak, aslında bunları her tür için yöntem olarak uygulamaktan daha az koddur. Bireysel vakalar hakkında tartışılabilir ama bu Python'un bir parçası ve bu tür temel değişiklikler yapmak için artık çok geç. Büyük kod kırılmalarını önlemek için işlevler kalmalıdır.

İlginç olsa da, yukarıdakiler hangi stratejinin benimseneceği konusunda pek bir şey söylemiyor.

Bunun nedenlerinden biri budur - özel yöntemlerle geliştiriciler, getLength (), length (), getlength () veya herhangi bir şekilde farklı bir yöntem adı seçmekte özgür olabilirler. Python, len () ortak işlevinin kullanılabilmesi için katı adlandırma uygular.

Biraz daha ilginç. Benim aldığım şey, işlevlerin bir anlamda arayüzlerin Pythonic versiyonu olduğudur.

Son olarak, Guido'nun kendisinden :

Yetenekler / Arayüzler hakkında konuşmak, bana bazı "haydut" özel yöntem isimlerimizi düşündürdü. Dil Referansında, "Bir sınıf, yöntemleri özel adlarla tanımlayarak (aritmetik işlemler veya alt simge oluşturma ve dilimleme gibi) özel sözdizimi tarafından çağrılan belirli işlemleri uygulayabilir." Ancak , sözdizimini desteklemek yerine yerleşik işlevlerin yararına sağlanmış gibi görünen __len__veya gibi özel adlara sahip tüm bu yöntemler vardır __unicode__. Böylece Muhtemelen bir arayüz tabanlı Python bu yöntemler, bir ABC düzenli adlandırılmış yöntemlerle dönüşeceğini __len__olacak

class container:
  ...
  def len(self):
    raise NotImplemented

Yine de, biraz daha düşünürsek, neden tüm sözdizimsel işlemlerin belirli bir ABC'de normal olarak adlandırılan uygun yöntemi çağırmadığını <object.lessthancomparable.lessthananlamıyorum . Örneğin " ", muhtemelen " " (veya belki " ") anlamına gelir. Bu yüzden bir başka fayda, Python'u bu karışık addaki tuhaflıktan uzaklaştırma yeteneği olacaktır ki bu bana bir HCI iyileştirmesi gibi görünüyor .

Hm. Katıldığımdan emin değilim (bunu düşün :-).

İlk olarak açıklamak istediğim iki bitlik "Python mantığı" var.

Her şeyden önce, HCI nedenleriyle x.len () yerine len (x) 'i seçtim ( def __len__()çok sonra geldi). Aslında iç içe geçmiş iki neden var, her ikisi de HCI:

(a) Bazı işlemler için, önek gösterimi sadece sonekten daha iyi okunur - önek (ve ek!) işlemleri matematikte, matematikçinin bir problem hakkında düşünmesine yardımcı olan görsellerin gösterimlerden hoşlandığı uzun bir matematik geleneğine sahiptir. Biz böyle bir formül yeniden hangi ile kolay karşılaştırın x*(a+b)içine x*a + x*bham OO gösterimi kullanarak aynı şeyi yapmanın sakarlık.

(b) Ben diyor kod okuduğumda len(x)ben biliyorum bir şeyin uzunluğu istiyor söyledi. Bu bana iki şey anlatıyor: sonuç bir tamsayı ve argüman bir tür kapsayıcı. Aksine, okuduğumda x.len(), bunun xbir arayüz uygulayan veya standardı olan bir sınıftan miras alan bir tür konteyner olduğunu zaten bilmem gerekiyor len(). Eşleme uygulamayan bir sınıfın bir get()veya keys() yöntemi olduğunda veya dosya olmayan bir şeyin bir write()yöntemi olduğunda zaman zaman yaşadığımız kafa karışıklığına tanık olun .

Aynı şeyi başka bir şekilde söyleyerek, 'len'i yerleşik bir işlem olarak görüyorum . Bunu kaybetmekten nefret ederim. Bunu kast edip etmediğini kesin olarak söyleyemem ama 'def len (self): ...' kesinlikle kulağa sıradan bir yönteme indirgemek istiyormuşsun gibi geliyor. Ben kesinlikle -1'im.

Açıklamaya söz verdiğim Python mantığının ikinci kısmı, __special__sadece bakmak için değil , özel yöntemler seçmemin nedenidir special. Sınıfların geçersiz kılmak isteyebileceği birçok işlem bekliyordum, bazı standartlar (örneğin __add__veya __getitem__), bazıları çok standart değil (örneğin __reduce__uzun süredir turşuların C kodunda hiçbir desteği yoktu). Bu özel işlemlerin sıradan yöntem adlarını kullanmasını istemedim, çünkü o zaman önceden var olan sınıflar veya tüm özel yöntemler için ansiklopedik bir belleği olmayan kullanıcılar tarafından yazılan sınıflar, uygulamak istemedikleri işlemleri yanlışlıkla tanımlayabilirler. , muhtemelen feci sonuçlarla. Ivan Krstić, tüm bunları yazdıktan sonra gelen mesajında ​​bunu daha kısa bir şekilde açıkladı.

- --Guido van Rossum (ana sayfa: http://www.python.org/~guido/ )

Bunu anladığım kadarıyla, bazı durumlarda ön ek notasyonu daha mantıklıdır (yani Duck.quack, dilbilimsel açıdan vakadan (Duck) daha mantıklıdır) ve yine, işlevler "arayüzlere" izin verir.

Böyle bir durumda, benim tahminim get_king_moves'u yalnızca Guido'nun ilk noktasına dayanarak uygulamak olacaktır. Ancak bu yine de, benzer push ve pop yöntemleriyle bir yığın ve kuyruk sınıfının uygulanmasıyla ilgili pek çok açık soru bırakıyor - bunlar işlev mi yoksa yöntem mi? (burada fonksiyonları tahmin ediyorum, çünkü gerçekten bir push-pop arayüzünü işaret etmek istiyorum)

TLDR: Biri, fonksiyonları ne zaman kullanacağına karar verme stratejisinin ne olması gerektiğini açıklayabilir mi?


2
Meh, bunu her zaman tamamen keyfi olarak düşünmüşümdür. Eğer olup olmadığını örtük "arayüzleri", o kadar fark yapmaz için ördek yazarak verir X.frobya X.__frob__ve ayaklı frob.
Cat Plus Plus

2
Çoğunlukla sizinle aynı fikirde olsam da, prensipte cevabınız Pythonic değil. Hatırlayın, "Belirsizlik karşısında, tahmin etme cazibesini reddedin." (Tabii ki, son tarihler bunu değiştirir, ancak bunu eğlence / kişisel gelişim için yapıyorum.)
Ceasar Bautista

Bu python hakkında sevmediğim bir şey. Bir dizeye int gibi cast yazmaya zorlayacaksanız, o zaman bunu bir yöntem haline getirin. Onu parantez içine almak zorunda kalmak sinir bozucu ve zaman alıcı.
Matt

1
Python'u sevmememin en önemli nedeni bu: Bir şeyi başarmak istediğinizde bir işlev mi yoksa bir yöntem mi aramanız gerektiğini asla bilemezsiniz. Ayrıca, vektörler veya veri çerçeveleri gibi yeni veri türlerine sahip ek kitaplıklar kullandığınızda daha da karmaşık hale gelir.
vonjd

1
"Zen of Python, bir şeyleri yapmanın yalnızca bir yolu olması gerektiğini belirtir" ancak bunun olmamasıdır.
2018

Yanıtlar:


84

Genel kuralım şudur - işlem nesne üzerinde mi yoksa nesne tarafından mı gerçekleştirilir?

nesne tarafından yapılıyorsa, üye işlem olmalıdır. Başka şeylere de uygulanabiliyorsa veya nesneye başka bir şey tarafından yapılıyorsa, o zaman bir işlev (veya belki başka bir şeyin üyesi) olmalıdır.

Programlamayı tanıtırken, nesneleri arabalar gibi gerçek dünya nesneleri açısından tanımlamak gelenekseldir (uygulama yanlış olsa da). Bir ördekten bahsediyorsun, öyleyse onunla devam edelim.

class duck: 
    def __init__(self):pass
    def eat(self, o): pass 
    def crap(self) : pass
    def die(self)
    ....

"Nesneler gerçektir" analojisi bağlamında, nesnenin yapabileceği her şey için bir sınıf yöntemi eklemek "doğrudur". Öyleyse bir ördeği öldürmek istediğimi söyleyin, ördeğe bir .kill () ekliyor muyum? Hayır ... bildiğim kadarıyla hayvanlar intihar etmez. Bu yüzden bir ördek öldürmek istiyorsam şunu yapmalıyım:

def kill(o):
    if isinstance(o, duck):
        o.die()
    elif isinstance(o, dog):
        print "WHY????"
        o.die()
    elif isinstance(o, nyancat):
        raise Exception("NYAN "*9001)
    else:
       print "can't kill it."

Bu benzetmeden uzaklaşacak olursak, neden yöntemler ve sınıflar kullanıyoruz? Çünkü verileri saklamak istiyoruz ve umarız kodumuzu gelecekte yeniden kullanılabilir ve genişletilebilir olacak şekilde yapılandırıyoruz. Bu bizi OO tasarımı için çok değerli olan kapsülleme kavramına getiriyor.

Kapsülleme ilkesi gerçekten de bunun geldiği şeydir: Bir tasarımcı olarak, herhangi bir kullanıcının veya başka bir geliştiricinin erişmesi kesinlikle gerekmeyen uygulama ve sınıf içi öğelerle ilgili her şeyi gizlemelisiniz. Sınıf örnekleriyle uğraştığımız için, bu " bu örnekte hangi işlemlerin çok önemli olduğuna" indirgenir . Bir işlem örneğe özgü değilse, üye işlev olmamalıdır.

TL; DR : @Bryan ne dedi. Bir örnek üzerinde çalışıyorsa ve sınıf örneğinin içindeki verilere erişmesi gerekiyorsa, bir üye işlev olmalıdır.


Kısacası, üye olmayan işlevler değişmez nesneler üzerinde çalışır, değiştirilebilir nesneler üye işlevleri kullanır? (Yoksa bu çok katı bir genelleme mi? Bu, yalnızca değişmez türlerin durumu olmadığı için belirli çalışmalar için.)
Ceasar Bautista

1
Katı bir OOP açısından, sanırım bu adil. Python hem genel hem de özel değişkenlere (__ ile başlayan değişkenler) sahip olduğundan ve Java'nın aksine sıfır garantili erişim koruması sağladığından, sadece izin veren bir dili tartıştığımız için kesinlik yoktur. Bununla birlikte, Java gibi daha az izin verilen bir dilde, getFoo () ad setFoo () işlevlerinin norm olduğunu ve dolayısıyla değişmezliğin mutlak olmadığını unutmayın. Müşteri kodunun üyelere atama yapmasına izin verilmez.
arrdem

1
@Ceasar Bu doğru değil. Değişmez nesnelerin durumu vardır; aksi takdirde herhangi bir tamsayıyı diğerinden ayıracak hiçbir şey olmazdı. Değişmez nesneler durumlarını değiştirmez . Genel olarak bu, tüm devletlerinin halka açık olmasını çok daha az sorunlu hale getirir. Ve bu ortamda, değişmez bir tamamen herkese açık nesneyi işlevlerle anlamlı bir şekilde işlemek çok daha kolaydır; bir yöntem olmanın "ayrıcalığı" yoktur.
Ben

1
@CeasarBautista evet, Ben haklı. 1) tasarlanmamış, 2) OOP ve 3) işlevsel kod tasarımının üç "büyük" okulu vardır. işlevsel bir tarzda, hiçbir durum yoktur. Çoğu python kodunun tasarlandığını görüyorum, girdi alıyor ve çok az yan etkiyle çıktı üretiyor. Nokta OOP her şeyin bir devlet olmasıdır. Sınıflar, durumlar için bir kapsayıcıdır ve bu nedenle "üye" işlevler duruma bağlı ve her çağrıldığında sınıfta tanımlanan durumu yükleyen yan etki tabanlı kodlardır. Python işlevsellik eğilimindedir, dolayısıyla üye olmayan kod tercih edilir.
arrdem

1
yemek (öz), saçmalık (öz), öl (öz). hahahaha
Wapiti

27

Aşağıdakileri yapmak istediğinizde bir sınıf kullanın:

1) Arama kodunu uygulama ayrıntılarından ayırın - soyutlama ve kapsüllemeden yararlanın .

2) Diğer nesnelerin yerine geçebilir olmak istediğinizde - polimorfizmden yararlanın .

3) Kodu benzer nesneler için yeniden kullanmak istediğinizde - kalıtımdan yararlanın .

Birçok farklı nesne türleri arasında mantıklı aramalar için işlevini kullanın - örneğin, yerleşik len ve repr fonksiyonları nesnelerin birçok türde için geçerlidir.

Bununla birlikte, seçim bazen bir zevk meselesine iner. Tipik aramalar için neyin en uygun ve okunabilir olduğunu düşünün. Örneğin, hangisi daha iyi olur (x.sin()**2 + y.cos()**2).sqrt()yoksa sqrt(sin(x)**2 + cos(y)**2)?


7

İşte basit bir pratik kural: Kod bir nesnenin tek bir örneğine etki ediyorsa, bir yöntem kullanın. Daha da iyisi: onu işlev olarak yazmak için zorlayıcı bir neden olmadıkça bir yöntemi kullanın.

Sizin özel örneğinizde, bunun şöyle görünmesini istiyorsunuz:

chessboard = Chessboard()
...
chessboard.get_king_moves()

Fazla düşünme. Kendi kendinize "bunu yöntem yapmak mantıklı değil" dediğiniz noktaya gelene kadar her zaman yöntemleri kullanın, bu durumda bir işlev yapabilirsiniz.


2
Yöntemleri neden işlevler yerine varsayılan olarak kullandığınızı açıklayabilir misiniz? (Ve bu kuralın yığın ve kuyruk / pop ve itme yöntemleri durumunda hala mantıklı olup olmadığını açıklayabilir misiniz?)
Ceasar Bautista

11
Bu pratik kural mantıklı değil. Standart kitaplığın kendisi, sınıfların işlevlere karşı ne zaman kullanılacağı konusunda bir rehber olabilir. heapq ve math işlevlerdir çünkü normal python nesneleri (yüzer ve listeler) üzerinde çalışırlar ve rastgele modülün yaptığı gibi karmaşık durumu sürdürmeleri gerekmez.
Raymond Hettinger

1
Standart kitaplık onu ihlal ettiği için kuralın anlamsız olduğunu mu söylüyorsunuz? Bu sonucun mantıklı olduğunu düşünmüyorum. Birincisi, genel kurallar sadece bu - mutlak kurallar değiller. Artı, standart kütüphanenin büyük bir kısmı yok başparmak bu kuralı uygulayın. OP bazı basit yönergeler arıyordu ve bence benim tavsiyem yeni başlayan biri için çok iyi. Yine de bakış açınızı takdir ediyorum.
Bryan Oakley

Eh, STL'nin bunu yapmak için bazı iyi nedenleri var. Math ve co için herhangi bir duruma sahip olmadığımız için bir sınıfa sahip olmanın faydası olmadığı açıktır (diğer dillerde bunlar sadece statik fonksiyonları olan sınıflardır). Örneğin, birkaç farklı kapta çalışan şeyler için len(), tasarımın mantıklı olduğu da tartışılabilir, ancak ben şahsen işlevlerin orada da çok kötü olmayacağını düşünürdüm - sadece len()her zaman bir tamsayı (ancak backcomp'un tüm ek sorunlarıyla birlikte bunu python için de savunmam)
Voo

-1, çünkü bu cevap tamamen keyfi: esasen X'in Y'den daha iyi olduğunu varsayarak X'in Y'den daha iyi olduğunu göstermeye çalışıyorsunuz
2018'de

7

Genelde kişi gibi bir nesne düşünüyorum.

Öznitelikler kişinin adı, boyu, ayakkabı numarası vb.

Yöntemler ve işlevler , kişinin gerçekleştirebileceği işlemlerdir.

Operasyon, bu belirli kişiye özel herhangi bir şey gerektirmeden (ve bu belirli kişide hiçbir şeyi değiştirmeden) herhangi bir eski kişi tarafından yapılabiliyorsa, bu bir işlevdir ve bu şekilde yazılmalıdır.

Bir operasyon kişiye etki ediyorsa (örneğin yemek yemek, yürümek, ...) veya bu kişiye özgü bir şeyin dahil olmasını gerektiriyorsa (dans etmek, kitap yazmak, ... gibi), o zaman bir yöntem olmalıdır .

Elbette, bunu birlikte çalıştığınız belirli nesneye çevirmek her zaman önemsiz değildir, ancak bunu düşünmenin iyi bir yolu olduğunu düşünüyorum.


ama herhangi bir yaşlı kişinin boyunu ölçebilirsiniz, yani bu mantıkla ölçülmeli height(person), değil person.heightmi?
endolith

@endolith Elbette, ama bir özellik olarak boyun daha iyi olduğunu söyleyebilirim çünkü onu geri almak için herhangi bir süslü iş yapmanıza gerek yok. Bir sayıyı almak için bir işlev yazmak, atlamak için gereksiz çemberler gibi görünür.
Thriveth

@endolith Öte yandan, eğer kişi büyüyüp boyunu değiştiren bir çocuksa, bir işlev değil, bir yöntemin onu halletmesine izin verilmesi açık olacaktır.
Gelişme

Bu doğru değil. Ya varsa are_married(person1, person2)? Bu sorgu çok geneldir ve bu nedenle bir yöntem değil, bir işlev olmalıdır.
Pithikos

@Pithikos Elbette, ancak bu aynı zamanda bir Nesnede (Kişi) gerçekleştirilen bir öznitelik veya eylemden daha fazlasını içerir, daha ziyade iki nesne arasındaki bir ilişkiyi içerir.
2014

5

Genel olarak, bazı şeyler için mantıksal bir yetenek seti uygulamak için sınıfları kullanırım , böylece programımın geri kalanında, onun uygulanmasını oluşturan tüm küçük endişeler hakkında endişelenmek zorunda kalmadan , bir şey hakkında mantık yürütebilirim .

"Bir şeyle ne yapabilirsin" temel soyutlamasının parçası olan her şey genellikle bir yöntem olmalıdır. Bu genellikle bir değiştirebileceğini her şeyi içerir şey dahili veri devlet genellikle "Bir ile neler yapabileceğini mantıksal düşüncenin parçası özel olarak kabul edilmez olarak, bir şey ".

Daha üst düzey operasyonlara geldiğinizde, özellikle birden fazla şeyi içeriyorlarsa , içsel öğelere özel erişime ihtiyaç duymadan bir şeyin kamusal soyutlamasından inşa edilebiliyorlarsa, genellikle en doğal olarak işlevler olarak ifade edildiğini görüyorum. başka bir nesnenin yeniden yöntemleri). Bu tamamen nasıl benim bir iç bölümü yeniden yazmak karar verirken bu büyük yarar şey (arayüzünü değiştirmeden) eserleri, sadece daha sonra bu yöntemleri açısından yazılı tüm harici fonksiyonlarını yeniden yazmak için yöntemlerin küçük bir çekirdek kümesi vardır ve Sadece Çalışacak. X sınıfı ile yapılacak tüm işlemlerin X sınıfındaki yöntemler olduğu konusunda ısrar etmenin aşırı karmaşık sınıflara yol açtığını görüyorum.

Yine de yazdığım koda bağlı. Bazı programlar için, etkileşimleri programın davranışına neden olan nesnelerin bir koleksiyonu olarak modelliyorum; burada en önemli işlevsellik tek bir nesneye yakından bağlıdır ve bu nedenle, yardımcı program işlevlerinin saçılmasıyla yöntemlerde uygulanır. Diğer programlar için en önemli şey, verileri işleyen bir dizi işlevdir ve sınıflar yalnızca işlevler tarafından işlenen doğal "ördek türlerini" uygulamak için kullanılır.


0

"Belirsizlik karşısında tahmin etme cazibesini reddedin" diyebilirsiniz.

Ancak bu bir tahmin bile değil. Sorununuzu çözdükleri için her iki yaklaşımın da sonuçlarının aynı olduğundan kesinlikle eminiz.

Hedeflere ulaşmak için birden fazla yola sahip olmanın yalnızca iyi bir şey olduğuna inanıyorum. Size alçakgönüllülükle, diğer kullanıcıların daha önce yaptığı gibi , dil açısından hangisinin "tadı daha iyi" / daha sezgisel hissettiren hangisini kullanacağınızı söyleyeceğim .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.