'Yükselt NotImplementedError' ne zaman kullanılır?


103

Kendinize ve ekibinize sınıfı doğru bir şekilde uygulamayı hatırlatmak için mi? Bunun gibi soyut bir sınıftan tam olarak yararlanamıyorum:

class RectangularRoom(object):
    def __init__(self, width, height):
        raise NotImplementedError

    def cleanTileAtPosition(self, pos):
        raise NotImplementedError

    def isTileCleaned(self, m, n):
        raise NotImplementedError


2
Şunu söyleyebilirim: "En az şaşkınlık ilkesini" tatmin ettiğinde .
MSeifert

3
Belgelere göre Uriel'in açık cevabı ve Jérôme'ın soyut temel sınıflarla ilgili katma değer cevabı ile kanıtlandığı üzere bu yararlı bir sorudur.
Davos

Ayrıca, türetilmiş bir sınıfın kasıtlı olarak iki yönlü bir koruma sunabilen bir temel sınıf soyut yöntemi uygulamadığını belirtmek için de kullanılabilir. Aşağıya bakın .
pfabri

Yanıtlar:


78

Belgelerde belirtildiği gibi [docs] ,

Kullanıcı tanımlı temel sınıflarda, soyut yöntemler, yöntemi geçersiz kılmak için türetilmiş sınıflara ihtiyaç duyduklarında veya sınıf, gerçek uygulamanın hala eklenmesi gerektiğini belirtmek için geliştirilirken bu istisnayı ortaya çıkarmalıdır.

Belirtilen ana kullanım durumu, bu hata, miras alınan sınıflarda uygulanması gereken soyut yöntemlerin göstergesi olsa da, bunu bir TODOişaretçi belirtmek gibi istediğiniz şekilde kullanabilirsiniz .


6
Soyut yöntemlerle, ben kullanıma tercih abc(bkz cevabımı ).
Jérôme

@ Jérôme neden ikisini birden kullanmıyorsunuz? İle süsleyin abstractmethodve yükselmesine izin verin NotImplementedError. Bu , türetilmiş sınıfların super().method()uygulanmasını yasaklar method.
timgeb

@timgeg Super (). method () 'u yasaklama gereği duymuyorum.
Jérôme

50

As Uriel diyor , bu çocuk sınıfta uygulanması gereken soyut bir sınıfta bir yöntem içindir, ama aynı zamanda bir YAPıLACAK belirtmek için kullanılabilir.

İlk kullanım durumu için bir alternatif var: Soyut Temel Sınıflar . Bunlar soyut sınıflar oluşturmaya yardımcı olur.

İşte bir Python 3 örneği:

class C(abc.ABC):
    @abstractmethod
    def my_abstract_method(self, ...):
        ...

Örnekleme yaparken , soyut olduğu Ciçin bir hata alırsınız my_abstract_method. Bunu bir çocuk sınıfında uygulamanız gerekir.

TypeError: Can't instantiate abstract class C with abstract methods my_abstract_method

Alt sınıf Cve uygulama my_abstract_method.

class D(C):
    def my_abstract_method(self, ...):
        ...

Şimdi somutlaştırabilirsiniz D.

C.my_abstract_methodboş olması gerekmez. Bu çağrılabilir Dkullanarak super().

Bunun bir avantajı , yöntem çağrısı zamanında değil, örnekleme zamanında NotImplementedErroraçık bir şekilde Exceptionalmanızdır.


2
Python 2.6+ sürümünde de mevcuttur. Sadece from abc import ABCMeta, abstractmethodve ABC'nizi ile tanımlayın __metaclass__ = ABCMeta. Dokümanlar: docs.python.org/2/library/abc.html
BoltzmannBrain

Bunu bir meta sınıfı tanımlayan bir sınıfla kullanmak istiyorsanız, hem sınıfın orijinal metasınıfını hem de ABCMeta'yı miras alan yeni bir meta sınıf oluşturmanız gerekir.
rabbit.aaron

26

Bunun yerine şunu düşünün:

class RectangularRoom(object):
    def __init__(self, width, height):
        pass

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass

ve alt sınıfa ayırır ve ona nasıl yapılacağını isTileCleaned()veya belki daha büyük olasılıkla yazım hatası yapacağını söylemeyi unutursunuz isTileCLeaned(). Ardından kodunuzda, onu aradığınızda bir alacaksınız None.

  • İstediğiniz geçersiz kılınan işlevi alacak mısınız? Kesinlikle hayır.
  • Mi Nonegeçerli çıkış? Kim bilir.
  • Bu amaçlanan davranış mı? Neredeyse kesinlikle değil.
  • Bir hata alacak mısınız? Değişir.

raise NotImplmentedError güçler bunun gibi, bunu uygulamak için olacak sen bunu gerçekleştirene kadar çalıştırmayı denediğinizde bir özel durum. Bu, birçok sessiz hatayı ortadan kaldırır. Bu, çıplak bir dışının neredeyse hiçbir zaman iyi bir fikir olmamasına benzer : çünkü insanlar hata yapar ve bu onların halının altına süpürülmemesini sağlar.

Not: Soyut bir temel sınıf kullanmak, diğer yanıtların da bahsettiği gibi, daha da iyidir, çünkü hatalar önden yüklenir ve program siz onları uygulayana kadar çalışmaz (NotImplementedError ile, gerçekten çağrılırsa yalnızca bir istisna atar).


11

Bir de yapabileceğini raise NotImplementedError() içindeki bir çocuk yöntemiyle @abstractmethod-dekore temel sınıf yöntemiyle.


Bir ölçüm modülleri ailesi (fiziksel cihazlar) için bir kontrol komut dosyası yazdığınızı hayal edin. Her modülün işlevselliği dar bir şekilde tanımlanmıştır ve yalnızca bir özel işlevi yerine getirir: biri bir dizi röle, diğeri çok kanallı bir DAC veya ADC, diğeri bir ampermetre olabilir.

Kullanılmakta olan düşük seviyeli komutların çoğu, örneğin ID numaralarını okumak veya onlara bir komut göndermek için modüller arasında paylaşılacaktır. Bakalım bu noktada elimizde ne var:

Temel Sınıf

from abc import ABC, abstractmethod  #< we'll make use of these later

class Generic(ABC):
    ''' Base class for all measurement modules. '''

    # Shared functions
    def __init__(self):
        # do what you must...

    def _read_ID(self):
        # same for all the modules

    def _send_command(self, value):
        # same for all the modules

Paylaşılan Fiiller

Ardından, modüle özgü komut fiillerinin çoğunun ve dolayısıyla arayüzlerinin mantığının da paylaşıldığını fark ederiz. Burada, bir dizi hedef modül düşünüldüğünde anlamı kendi kendini açıklayan 3 farklı fiil var.

  • get(channel)

  • röle: rölenin açık / kapalı durumunu alchannel

  • DAC: get çıkış voltajı üzerindechannel

  • ADC: get giriş voltajını üzerindechannel

  • enable(channel)

  • röle: rölenin açık olarak kullanılmasını etkinleştirchannel

  • DAC: çıkış kanalının kullanımını etkinleştirinchannel

  • ADC: giriş kanalının kullanımını etkinleştirinchannel

  • set(channel)

  • röle: röleyi channelaçık / kapalı olarak ayarlayın

  • DAC: çıkış voltajını ayarlayınchannel

  • ADC: hmm ... akla mantıklı bir şey gelmiyor.


Paylaşılan Fiiller Zorunlu Fiiller Oluyor

Yukarıdaki fiillerin, anlamlarının her biri için açık olduğunu gördüğümüzde modüller arasında paylaşılması için güçlü bir durum olduğunu iddia ediyorum. Temel sınıfımı şöyle yazmaya devam edeceğim Generic:

class Generic(ABC):  # ...continued
    
    @abstractmethod
    def get(self, channel):
        pass

    @abstractmethod
    def enable(self, channel):
        pass

    @abstractmethod
    def set(self, channel):
        pass

Alt sınıflar

Artık alt sınıflarımızın hepsinin bu yöntemleri tanımlaması gerektiğini biliyoruz. ADC modülü için neye benzeyebileceğini görelim:

class ADC(Generic):

    def __init__(self):
        super().__init__()  #< applies to all modules
        # more init code specific to the ADC module
    
    def get(self, channel):
        # returns the input voltage measured on the given 'channel'

    def enable(self, channel):
        # enables accessing the given 'channel'

Şimdi merak ediyor olabilirsiniz:

Ancak bu, ADC modülü için işe yaramayacak, çünkü setyukarıda gördüğümüz gibi orada hiçbir anlam ifade etmiyor!

Haklısın: setPython, ADC nesnenizi başlatmaya çalıştığınızda aşağıdaki hatayı tetikleyeceği için uygulama yapmamak bir seçenek değildir.

TypeError: Can't instantiate abstract class 'ADC' with abstract methods 'set'

Bir şey uygulamalıdır Yani yaptığımız çünkü setbir fiil zorunlu olarak değil, aynı zamanda, aynı zamanda herhangi bir şey uygulayamaz gereken iki diğer modüller tarafından paylaşıldığı, (aka '@abstractmethod') setbu özel modül için bir anlam ifade etmiyor.

NotImplementedError to the Rescue

ADC sınıfını şu şekilde tamamlayarak:

class ADC(Generic): # ...continued

    def set(self, channel):
        raise NotImplementedError("Can't use 'set' on an ADC!")

Aynı anda çok iyi üç şey yapıyorsunuz:

  1. Bir kullanıcıyı yanlışlıkla bu modül için uygulanmayan (ve uygulanmaması gereken!) Bir komut ('set') vermekten koruyorsunuz.
  2. Onlara sorunun ne olduğunu açıkça söylüyorsunuz (bunun neden önemli olduğunu öğrenmek için TemporalWolf'un 'Çıplak istisnalar' bağlantısına bakın)
  3. Zorunlu fiillerin anlamlı olduğu diğer tüm modüllerin uygulanmasını koruyorsunuz . Yani bu fiiller kendisi için bu modüllerin sağlamak yapmak mantıklı bu yöntemleri uygulayacak ve bu tam olarak bu fiilleri kullanarak bunu yapacağız değil diğer bazı geçici isimler.

-1

@propertyDekoratörü kullanmak isteyebilirsin ,

>>> class Foo():
...     @property
...     def todo(self):
...             raise NotImplementedError("To be implemented")
... 
>>> f = Foo()
>>> f.todo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in todo
NotImplementedError: To be implemented

13
Bunun soruyu nasıl ele aldığını anlamıyorum, ki bu ne zaman kullanılacağıdır .
TemporalWolf
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.