Python neden 'sihirli yöntemler' kullanır?


99

Son zamanlarda Python ile uğraşıyorum ve biraz tuhaf bulduğum bir şey, 'sihirli yöntemlerin' yaygın kullanımı, örneğin uzunluğunu kullanılabilir kılmak için, bir nesne bir yöntem uygular def __len__(self)ve sonra sen yaz len(obj).

Sadece nesnelerin neden basitçe bir len(self)yöntemi tanımlamadığını ve doğrudan nesnenin bir üyesi olarak çağrılmadığını merak ediyordum , örneğin obj.len()? Eminim Python'un bunu yaptığı gibi yapmasının iyi nedenleri olmalı, ama bir acemi olarak bunların ne olduğunu henüz çözmedim.


4
Ben genel nedeni gibi)) tarihsel ve b şey olduğunu düşünüyorum len()ya reversed()nesnelerin birçok türleri için de geçerlidir, fakat bir yöntem gibi append()yalnızca vb dizileri için de geçerlidir
Grant Paul

Yanıtlar:


64

AFAIK, lenbu açıdan özeldir ve tarihi köklere sahiptir.

İşte bir alıntı SSS bölümünden :

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, şekilsiz 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şiklikleri yapmak için artık çok geç. Büyük kod kırılmalarını önlemek için işlevler kalmalıdır.

Diğer "sihirli yöntemler" (aslında Python folklorunda özel yöntem olarak adlandırılır ) çok anlam ifade eder ve diğer dillerde de benzer işlevler mevcuttur. Çoğunlukla özel sözdizimi kullanıldığında örtük olarak çağrılan kod için kullanılırlar.

Örneğin:

  • aşırı yüklenmiş operatörler (C ++ ve diğerlerinde mevcuttur)
  • kurucu / yıkıcı
  • özniteliklere erişmek için kancalar
  • metaprogramlama araçları

ve bunun gibi...


2
Python ve En Az Şaşkınlık Prensibi, Python'un bu şekilde olmasının bazı avantajları için iyi bir okuma kitabıdır (yine de İngilizce ihtiyaçlarının işe yaradığını kabul etmeme rağmen). Temel nokta: standart kütüphanenin çok, çok yeniden kullanılabilir hale gelen ancak yine de geçersiz kılınabilen bir ton kodu uygulamasına izin verir.
jpmc26

20

Python Zen'den:

Belirsizlik karşısında, tahmin etme cazibesini reddedin.
Bunu yapmanın bir - ve tercihen sadece bir - açık yolu olmalıdır.

Özel yöntemlerle, geliştiriciler farklı bir yöntem adı gibi seçmekte özgür olacak - Bu nedenlerden biridir getLength(), length(), getlength()ya da ne. Python, ortak işlevin len()kullanılabilmesi için katı adlandırma uygular .

Birçok nesne türü için ortak olan tüm işlemler __nonzero__, __len__veya gibi sihirli yöntemlere konur __repr__. Yine de çoğunlukla isteğe bağlıdırlar.

Operatör aşırı yükleme aynı zamanda sihirli yöntemlerle de yapılır (örn. __le__), Bu nedenle bunları diğer yaygın işlemler için kullanmak da mantıklıdır.


Bu zorlayıcı bir argümandır. "Guido'nun OO'ya gerçekten inanmaması" daha tatmin edici ... (başka yerde iddia ettiğim gibi).
Andy Hayden

15

Python "sihirli yöntemler" kelimesini kullanır , çünkü bu yöntemler programınız için gerçekten sihir yapar. Python'un sihirli yöntemlerini kullanmanın en büyük avantajlarından biri, nesnelerin yerleşik tipler gibi davranmasını sağlamak için basit bir yol sağlamasıdır. Bu, temel operatörleri gerçekleştirmenin çirkin, sezgisel olmayan ve standart olmayan yollarından kaçınabileceğiniz anlamına gelir.

Aşağıdaki bir örneği ele alalım:

dict1 = {1 : "ABC"}
dict2 = {2 : "EFG"}

dict1 + dict2
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Sözlük türü eklemeyi desteklemediğinden bu bir hata verir. Şimdi sözlük sınıfını genişletelim ve "__add__" sihirli yöntemini ekleyelim :

class AddableDict(dict):

    def __add__(self, otherObj):
        self.update(otherObj)
        return AddableDict(self)


dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})

print (dict1 + dict2)

Şimdi aşağıdaki çıktıyı veriyor.

{1: 'ABC', 2: 'EFG'}

Böylece, bu yöntemi ekleyerek, aniden sihir oldu ve daha önce aldığınız hata ortadan kalktı.

Umarım her şeyi size açıklar. Daha fazla bilgi için, bakınız:

Python'un Sihirli Yöntemleri Rehberi (Rafe Kettler, 2012)


9

Bu işlevlerden bazıları, tek bir yöntemin uygulayabileceğinden daha fazlasını yapar (bir üst sınıfta soyut yöntemler olmadan). Örneğin şöyle bool()davranır:

def bool(obj):
    if hasattr(obj, '__nonzero__'):
        return bool(obj.__nonzero__())
    elif hasattr(obj, '__len__'):
        if obj.__len__():
            return True
        else:
            return False
    return True

Ayrıca, bool()bunun her zaman Doğru veya Yanlış döndüreceğinden % 100 emin olabilirsiniz ; Bir yönteme güvenirseniz, neyi geri alacağınızdan tam olarak emin olamazsınız.

Görece karmaşık uygulamalara sahip diğer bazı işlevler (temeldeki sihirli yöntemlerden daha karmaşık olması muhtemeldir) iter()ve cmp()ve tüm öznitelik yöntemleridir ( getattr, setattrve delattr). intZorlama yaparken (uygulayabilirsiniz __int__) sihirli yöntemlere de erişmek gibi şeyler , ancak tür olarak çift görev yapar. len(obj)aslında hiçbir zaman farklı olduğuna inanmadığım tek durum obj.__len__().


2
Yerine hasattr()ben kullanırım try:/ except AttributeError:ve yerine if obj.__len__(): return True else: return Falsesadece söyleyebilirim return obj.__len__() > 0ancak bu sadece üslup şeylerdir.
Chris Lutz

Python 2.6'da (btw'ye bool(x)atıfta bulunulur x.__nonzero__()), yönteminiz çalışmaz. bool örneklerinin bir yöntemi vardır __nonzero__()ve kodunuz, obj bir bool olduğunda kendini çağırmaya devam eder. Belki de bool(obj.__bool__())aynı şekilde davranılmalı __len__? (Yoksa bu kod aslında Python 3 için mi çalışıyor?)
Ponkadoodle

Bool () 'un döngüsel doğası, tanımın alışılmadık döngüsel doğasını yansıtmak için kasıtlı olarak biraz saçmadır. Basitçe ilkel olarak görülmesi gerektiğine dair bir tartışma var.
Ian Bicking

Aradaki tek fark (şu anda) len(x)ve arasındaki tek fark x.__len__(), birincisinin aşan uzunluklar için OverflowError'ı yükselteceği sys.maxsize, ikincisi ise Python'da uygulanan türler için genellikle olmayacağıdır. Yine de bu, bir özellikten çok bir hatadır (örneğin, Python 3.2'nin menzil nesnesi çoğunlukla keyfi olarak büyük aralıkları işleyebilir, ancak lenonlarla birlikte kullanmak başarısız olabilir. __len__Yine de, Python yerine C'de uygulandıkları için başarısız olurlar)
ncoghlan

4

Gerçekten "sihirli isimler" değiller. Bu sadece bir nesnenin belirli bir hizmeti sağlamak için uygulaması gereken arayüzdür. Bu anlamda, yeniden uygulamanız gereken önceden tanımlanmış herhangi bir arayüz tanımından daha sihirli değildirler.


1

Sebep çoğunlukla tarihsel olsa da, Python'da lenbir yöntem yerine bir işlevin kullanımını uygun kılan bazı özellikler vardır .

Python'daki bazı işlemler yöntem olarak uygulanır, örneğin list.indexve dict.append, diğerleri ise çağrılabilirler ve sihirli yöntemler olarak uygulanır, örneğin strve iterve reversed. İki grup yeterince farklı olduğundan farklı yaklaşım haklı çıkar:

  1. Yaygındır.
  2. str, intVe arkadaşlar türleridir. Yapıcıyı aramak daha mantıklı.
  3. Gerçekleştirme, işlev çağrısından farklıdır. Örneğin, iterdiyebilirsiniz __getitem__eğer __iter__mevcut değildir ve bir yöntem çağrısında sığmayan ek argümanlar destekler. Aynı nedenle Python'un son sürümlerinde it.next()olarak değiştirildi next(it)- daha mantıklı.
  4. Bunlardan bazıları operatörlerin yakın akrabalarıdır. Çağırmak için sözdizimi vardır __iter__ve __next__buna fordöngü denir . Tutarlılık için bir işlev daha iyidir. Ve belirli optimizasyonlar için daha iyi hale getirir.
  5. Bazı işlevler, bir şekilde diğerlerine çok benziyor - yaptığı reprgibi davranıyor str. Having str(x)karşı x.repr()kafa karıştırıcı olurdu.
  6. Örneğin bazıları nadiren gerçek uygulama yöntemini kullanır isinstance.
  7. Bazıları gerçek operatörleri, getattr(x, 'a')yapmanın başka bir yoludur x.ave getattryukarıda belirtilen niteliklerin çoğunu paylaşır.

Ben şahsen birinci grup yöntemine benzer ve ikinci gruba operatör benzeri diyorum. Bu çok iyi bir ayrım değil ama umarım bir şekilde yardımcı olur.

Bunu lensöyledikten sonra, ikinci gruba tam olarak uymuyor. İlkindeki operasyonlara daha yakın, tek fark, neredeyse hepsinden çok daha yaygın olmasıdır. Ama yaptığı tek şey çağırmak __len__ve ona çok yakın L.index. Ancak bazı farklılıklar var. Örneğin, __len__diğer özelliklerin uygulanması için çağrılabilir, örneğin bool, yöntem çağrıldıysa , tamamen farklı bir şey yapan özel yöntemle lenkırılabilirsiniz .bool(x)len

Kısacası, nesne oluşturma sırasında sınıfların uygulayabileceği, özel bir işlev aracılığıyla (genellikle bir operatörün yapacağı gibi uygulamadan daha fazlasını yapan) bir operatör aracılığıyla erişilebilen çok yaygın bir dizi özelliğe sahipsiniz ve hepsi bazı ortak özellikleri paylaşmak. Geri kalan her şey bir yöntemdir. Ve lenbu kuralın bir şekilde istisnasıdır.


0

Yukarıdaki iki gönderiye eklenecek çok şey yok, ancak tüm "sihirli" işlevler hiç de sihirli değil. Yorumlayıcı başladığında örtük / otomatik olarak içe aktarılan __ yerleşikler__ modülünün parçasıdırlar. Yani:

from __builtins__ import *

programınız başlamadan önce her seferinde gerçekleşir.

Python'un bunu yalnızca etkileşimli kabuk için yapmasının ve ihtiyaç duydukları yerleşiklerden çeşitli parçaları içe aktarmak için komut dosyalarına ihtiyaç duymasının her zaman daha doğru olacağını düşündüm. Ayrıca muhtemelen farklı __ ana__ işleme, kabuklarda ve etkileşimli olarak iyi olurdu. Her neyse, tüm işlevleri kontrol edin ve onlar olmadan nasıl bir şey olduğunu görün:

dir (__builtins__)
...
del __builtins__
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.