Python'da, bir yöntemi geçersiz kıldığımı nasıl gösterebilirim?


173

Örneğin Java'da @Overrideek açıklama yalnızca bir geçersiz kılma derleme zamanı denetimi sağlamakla kalmaz, aynı zamanda mükemmel kendi kendini belgeleyen kod sağlar.

Ben sadece belgeleri arıyorum (pylint gibi bazı denetleyicisi için bir gösterge olsa da, bu bir bonus). Bir yere yorum veya doktrin ekleyebilirim, ancak Python'da bir geçersiz kılma belirtmek için deyimsel yol nedir?


13
Başka bir deyişle, bir yöntemi geçersiz kıldığınızı hiç göstermiyor musunuz? Bunu anlamaya okuyucuya mı bıraktınız?
Bluu

2
Evet, derlenmiş bir dilden gelen hataya eğilimli bir durum gibi göründüğünü biliyorum, ama sadece kabul etmelisin. Uygulamada çok fazla sorun bulamadım (benim durumumda Ruby, Python değil, aynı fikir)
Ed S.

Tabi bitti. Triptych'in yanıtı ve mkorpela'nın cevapları basit, bunu beğendim, ancak ikincisinin açık üstü örtülü ruhu ve akıllıca hataları önleme kazanıyor.
Bluu

1
Doğrudan aynı değildir, ancak soyut temel sınıflar , tüm soyut yöntemlerin bir alt sınıf tarafından geçersiz kılındığını kontrol eder. Elbette somut yöntemleri geçersiz kılarsanız bu yardımcı olmaz.
letmaik

Yanıtlar:


208

Buna ve fwc: s cevabına dayanarak pip ile kurulabilir bir paket oluşturdum https://github.com/mkorpela/overrides

Zaman zaman burada bu soruya bakıyorum. Temelde bu, kod tabanımızda aynı hatayı gördükten sonra tekrar olur: Birisi "arayüz" de bir yöntemi yeniden adlandırırken bazı "arayüz" uygulama sınıfını unutmuştur.

Python Java değil, ama Python'un gücü var - ve açıkça örtük olmaktan daha iyidir - ve gerçek dünyada bu şeyin bana yardımcı olacağı gerçek somut durumlar var.

İşte burada geçersiz kılma dekoratörünün bir taslağı. Bu, parametre olarak verilen sınıfın, dekore edilen yöntemle aynı yöntem (veya bir şey) adına sahip olup olmadığını kontrol eder.

Daha iyi bir çözüm düşünebiliyorsanız lütfen buraya gönderin!

def overrides(interface_class):
    def overrider(method):
        assert(method.__name__ in dir(interface_class))
        return method
    return overrider

Aşağıdaki gibi çalışır:

class MySuperInterface(object):
    def my_method(self):
        print 'hello world!'


class ConcreteImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def my_method(self):
        print 'hello kitty!'

ve hatalı bir sürüm yaparsanız, sınıf yüklemesi sırasında bir onaylama hatası ortaya çıkar:

class ConcreteFaultyImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def your_method(self):
        print 'bye bye!'

>> AssertionError!!!!!!!

18
Muhteşem. Bu, ilk denediğimde bir yazım hatası yakaladı. Kudos.
Christopher Bruns

7
mfbutner: yöntem her yürütüldüğünde çağrılmaz - yalnızca yöntem oluşturulduğunda.
mkorpela

3
Bu doktor dizeleri için de güzel! overridesgeçersiz kılma yönteminin kendi yöntemi yoksa, geçersiz kılınan yöntemin öğretisini kopyalayabilir.
letmaik

5
@mkorpela, Heh bu kodunuz python varsayılan lib sisteminde olmalıdır. Bunu neden pip sistemine koymuyorsun? : P

5
@mkorpela: Oh ve ben python çekirdek geliştiricileri bu paket hakkında bilgilendirmenizi öneririz, onlar çekirdek python sistemine overide dekoratör ekleme hakkında düşünmek isteyebilirsiniz. :)

30

Burada interface_class adının belirtilmesini gerektirmeyen bir uygulama var.

import inspect
import re

def overrides(method):
    # actually can't do this because a method is really just a function while inside a class def'n  
    #assert(inspect.ismethod(method))

    stack = inspect.stack()
    base_classes = re.search(r'class.+\((.+)\)\s*\:', stack[2][4][0]).group(1)

    # handle multiple inheritance
    base_classes = [s.strip() for s in base_classes.split(',')]
    if not base_classes:
        raise ValueError('overrides decorator: unable to determine base class') 

    # stack[0]=overrides, stack[1]=inside class def'n, stack[2]=outside class def'n
    derived_class_locals = stack[2][0].f_locals

    # replace each class name in base_classes with the actual class type
    for i, base_class in enumerate(base_classes):

        if '.' not in base_class:
            base_classes[i] = derived_class_locals[base_class]

        else:
            components = base_class.split('.')

            # obj is either a module or a class
            obj = derived_class_locals[components[0]]

            for c in components[1:]:
                assert(inspect.ismodule(obj) or inspect.isclass(obj))
                obj = getattr(obj, c)

            base_classes[i] = obj


    assert( any( hasattr(cls, method.__name__) for cls in base_classes ) )
    return method

2
Biraz büyülü ama tipik kullanımı çok daha kolay hale getirir. Kullanım örnekleri ekleyebilir misiniz?
Bluu

belki de @classmethod veya @property gibi yerleşik bir dekoratörle karşılaştırma olarak ifade edilen bu dekoratörü kullanmanın ortalama ve en kötü maliyeti nedir?
larham1

4
@ larham1 Bu dekoratör, her çağrıda değil, sınıf tanımı analiz edildiğinde bir kez yürütülür. Bu nedenle, yürütme maliyeti, program çalışma süresine kıyasla önemsizdir.
Abgan

PEP 487 sayesinde Python 3.6'da çok daha güzel olacak .
Neil G

Daha iyi hata mesajı almak için: herhangi bir (hasat_tr (cls, method .__ name__) base_classes içindeki cls için), 'temel sınıfta "{}" geçersiz kılma yöntemi bulunamadı. ". Format (method .__ name__)
Ivan Kovtun

14

Bunu yalnızca dokümantasyon amacıyla istiyorsanız, kendi geçersiz kılma dekoratörünüzü tanımlayabilirsiniz:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

Bu, aslında geçersiz kılmayı kontrol edecek şekilde geçersiz kılma (f) oluşturmazsanız, göz şekerlemesinden başka bir şey değildir.

Ama sonra, bu Python, neden Java gibi yazıyorsun?


2
overrideDekoratöre muayene yoluyla gerçek doğrulama eklenebilir .
Erik Kaplun

70
Ama sonra, bu Python, neden Java gibi yazıyorsun? Java'daki bazı fikirler iyi ve diğer dillere yayılmaya değer olduğundan?
Piotr Dobrogost

9
Çünkü bir sınıfta bir yöntemi yeniden adlandırdığınızda, bazı alt sınıf 2 seviyelerinin bunu geçersiz kıldığını bilmek güzel olurdu. Elbette, kontrol etmek kolaydır, ancak dil ayrıştırıcısından biraz yardım zarar vermez.
Abgan

4
Çünkü bu iyi bir fikir. Çeşitli diğer dillerin bu özelliğe sahip olması, hem lehte hem de aleyhte bir argüman değildir.
sfkleach

6

Python Java değil. Elbette derleme zamanı kontrolü diye bir şey yoktur.

Bence öğretide bir yorum çoktur. Bu, yönteminizin herhangi bir kullanıcısının help(obj.method)yöntemin bir geçersiz kılma olduğunu yazmasına ve görmesine olanak tanır .

Ayrıca class Foo(Interface), kullanıcıların help(Interface.method)yönteminizin sağlamayı amaçladığı işlevsellik hakkında fikir sahibi olmalarını sağlayacak bir arabirimi de açıkça genişletebilirsiniz .


57
@OverrideJava'daki asıl nokta belgelemek değildir - bir yöntemi geçersiz kılmak istediğinizde bir hata yakalamaktır, ancak yeni bir tane tanımlamakla sonuçlanmıştır (örneğin, bir adı yanlış yazdığınız için; Java'da, yanlış imza, ancak bu Python'da bir sorun değil - ama yazım hatası hala var).
Pavel Minaev

2
@ Pavel Minaev: Doğru, ancak özellikle geçersiz kılmalar için otomatik göstergeleri olmayan bir IDE / metin düzenleyicisi kullanıyorsanız (Eclipse'nin JDT'si bunları örneğin satır numaralarının yanında düzgün bir şekilde gösterir).
Tuukka Mustonen

2
@PavelMinaev Yanlış. Ana noktalarından biri @Override, zaman kontrolünü derlemenin yanı sıra belgelerdir.
siamii

6
@siamii Bence belgelere bir yardım harika, ama gördüğüm tüm resmi Java belgelerinde, onlar sadece derleme zaman kontrolleri önemini gösterir. Lütfen Pavel'un "yanlış" olduğu iddianızı doğrulayın.
Andrew Mellinger

5

Doğaçlama mkorpela büyük cevap , İşte bir sürümü ile

daha kesin denetimler, adlandırma ve yükseltilmiş Hata nesneleri

def overrides(interface_class):
    """
    Function override annotation.
    Corollary to @abc.abstractmethod where the override is not of an
    abstractmethod.
    Modified from answer https://stackoverflow.com/a/8313042/471376
    """
    def confirm_override(method):
        if method.__name__ not in dir(interface_class):
            raise NotImplementedError('function "%s" is an @override but that'
                                      ' function is not implemented in base'
                                      ' class %s'
                                      % (method.__name__,
                                         interface_class)
                                      )

        def func():
            pass

        attr = getattr(interface_class, method.__name__)
        if type(attr) is not type(func):
            raise NotImplementedError('function "%s" is an @override'
                                      ' but that is implemented as type %s'
                                      ' in base class %s, expected implemented'
                                      ' type %s'
                                      % (method.__name__,
                                         type(attr),
                                         interface_class,
                                         type(func))
                                      )
        return method
    return confirm_override


Pratikte şöyle görünüyor:

NotImplementedError" temel sınıfta uygulanmadı "

class A(object):
    # ERROR: `a` is not a implemented!
    pass

class B(A):
    @overrides(A)
    def a(self):
        pass

daha açıklayıcı NotImplementedErrorhata ile sonuçlanır

function "a" is an @override but that function is not implemented in base class <class '__main__.A'>

tam yığın

Traceback (most recent call last):
  
  File "C:/Users/user1/project.py", line 135, in <module>
    class B(A):
  File "C:/Users/user1/project.py", line 136, in B
    @overrides(A)
  File "C:/Users/user1/project.py", line 110, in confirm_override
    interface_class)
NotImplementedError: function "a" is an @override but that function is not implemented in base class <class '__main__.A'>


NotImplementedError" beklenen uygulama türü "

class A(object):
    # ERROR: `a` is not a function!
    a = ''

class B(A):
    @overrides(A)
    def a(self):
        pass

daha açıklayıcı NotImplementedErrorhata ile sonuçlanır

function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>

tam yığın

Traceback (most recent call last):
  
  File "C:/Users/user1/project.py", line 135, in <module>
    class B(A):
  File "C:/Users/user1/project.py", line 136, in B
    @overrides(A)
  File "C:/Users/user1/project.py", line 125, in confirm_override
    type(func))
NotImplementedError: function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>




@Mkorpela cevabı ile ilgili en iyi şey, kontrolün bazı başlatma aşamasında gerçekleşmesidir. Kontrolün "çalıştırılması" gerekmez. Önceki örneklere atıfta bulunulduğunda class B, asla başlatılmaz ( B()), ancak yine de NotImplementedErrorartacaktır. Bu, overrideshataların daha erken yakalandığı anlamına gelir .


Hey! Bu ilginç görünüyor. Ipromise projem için bir çekme isteği yapmayı düşünebilir misiniz? Bir cevap ekledim.
Neil G

@NeilG Ipromise projesini çatalladım ve biraz kodladım . Görünüşe bakılırsa, bunu aslında içinde uyguladınız overrides.py. Eminim anlamlı gelen istisna türlerini değiştirmek dışında artırabilir başka ne değilim TypeErroriçin NotImplementedError.
JamesThomasMoon1979

Hey! Teşekkürler, geçersiz kılınan nesnenin aslında türü olduğunu kontrol yok types.MethodType. Cevabınızda bu iyi bir fikirdi.
Neil G

2

Diğerleri gibi Java aksine söylediler @Overide etiketi ancak yukarıda dekoratörler kullanarak kendi oluşturabilirsiniz ancak iç dict kullanmak yerine getattrib () global yöntem kullanmanızı öneririz böylece aşağıdaki gibi bir şey olsun:

def Override(superClass):
    def method(func)
        getattr(superClass,method.__name__)
    return method

İsterseniz kendi getattr () yakalamak olabilir kendi hata yakalamak yakalamak ama bu durumda getattr yöntemi daha iyi olduğunu düşünüyorum.

Ayrıca bu, sınıf yöntemleri ve vairabiller dahil bir sınıfa bağlı tüm öğeleri yakalar


2

@ Mkorpela'nın harika cevabına dayanarak, daha fazla kontrol yapan benzer bir paket ( ipromise pypi github ) yazdım :

Varsayalım Adevralır Bve C, Bdevralır C.

Modül ipromise aşağıdakileri kontrol eder:

  • Eğer A.fgeçersiz kılar B.f, B.fvar olmalıdır ve Adevralınmalıdır B. (Bu, geçersiz kılmalar paketindeki denetimdir).

  • Sen desen yok A.fo geçersiz kılar olduğunu beyan B.fsonra geçersiz kılar beyan, hangi C.f. Ao C.fzamandan beri geçersiz kıldığını, Bbu yöntemin geçersiz kılmayı durdurmaya karar verebileceğini ve bu durumun aşağı akış güncellemelerine neden olmaması gerektiğini belirtmelidir.

  • Sen desen yok A.fo geçersiz kılar beyan C.f, ancak B.fonun geçersiz kılmayı bildirmiyor.

  • Sen desen yok A.fo geçersiz kılar beyan C.f, ancak B.fbunun bazı geçersiz kılar olduğunu beyan D.f.

Aynı zamanda soyut bir metodun uygulanmasını işaretlemek ve kontrol etmek için çeşitli özelliklere sahiptir.


0

Hear en basit ve Java sınıflarıyla Jython altında çalışıyor:

class MyClass(SomeJavaClass):
     def __init__(self):
         setattr(self, "name_of_method_to_override", __method_override__)

     def __method_override__(self, some_args):
         some_thing_to_do()

0

Sadece yaptığım dekoratör, geçersiz kılma özelliğinin adının, bir üst sınıf belirtmeksizin, özniteliğin içinde bulunduğu sınıfın herhangi bir üst sınıfı olup olmadığını kontrol etmekle kalmadı, bu dekoratör, geçersiz kılma özelliğinin geçersiz kılınanla aynı tipte olduğundan emin olmak için kontrol etti özniteliği. Sınıf Metotları metotlar gibi, Statik Metotlar fonksiyonlar gibi muamele görürler. Bu dekoratör callables, sınıf yöntemleri, statik yöntemler ve özellikler için çalışır.

Kaynak kodu için bkz. Https://github.com/fireuser909/override

Bu dekoratör yalnızca override.OverridesMeta örnekleri olan sınıflar için çalışır, ancak sınıfınız özel bir metasınıf örneğiyse, geçersiz kılma dekoratörüyle uyumlu bir metasınıf oluşturmak için create_custom_overrides_meta işlevini kullanın. Testler için override .__ init__ modülünü çalıştırın.


0

Python 2.6+ ve Python 3.2+ sürümlerinde bunu yapabilirsiniz ( Aslında benzetin , Python işlev aşırı yüklemesini desteklemez ve alt sınıf ebeveynin yöntemini otomatik olarak geçersiz kılar). Bunun için Dekoratörler kullanabiliriz. Ama önce, Python @decoratorsve Java'nın @Annotationstamamen farklı şeyler olduğunu unutmayın. Birincisi somut kodlu bir sarıcı, daha sonra bir derleyici bayrağıdır.

Bunun için önce yapın pip install multipledispatch

from multipledispatch import dispatch as Override
# using alias 'Override' just to give you some feel :)

class A:
    def foo(self):
        print('foo in A')

    # More methods here


class B(A):
    @Override()
    def foo(self):
        print('foo in B')
    
    @Override(int)
    def foo(self,a):
        print('foo in B; arg =',a)
        
    @Override(str,float)
    def foo(self,a,b):
        print('foo in B; arg =',(a,b))
        
a=A()
b=B()
a.foo()
b.foo()
b.foo(4)
b.foo('Wheee',3.14)

çıktı:

foo in A
foo in B
foo in B; arg = 4
foo in B; arg = ('Wheee', 3.14)

Dekoratörü burada parantez ile kullanmanız gerektiğini unutmayın

Hatırlanması gereken bir şey, Python'un doğrudan aşırı yüke sahip olmadığı için, B Sınıfı A Sınıfından miras fooalmasa da, tüm bunlara ihtiyaç duysa bile @Override kullanmanız gerekir ('Aşırı Yük' takma adı kullanıldığında bu durumda daha iyi)

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.