Dengan Python, bagaimana saya menunjukkan saya mengganti metode?

173

Di Jawa, misalnya, @Overrideanotasi tidak hanya menyediakan pemeriksaan waktu kompilasi atas penggantian, tetapi juga membuat kode dokumentasi mandiri yang sangat baik.

Saya hanya mencari dokumentasi (walaupun jika ini merupakan indikator bagi beberapa pemeriksa seperti pylint, itu bonus). Saya dapat menambahkan komentar atau docstring di suatu tempat, tetapi apa cara idiomatis untuk menunjukkan override dalam Python?

Bluu
sumber
13
Dengan kata lain, Anda tidak pernah menunjukkan bahwa Anda mengganti metode? Serahkan pada pembaca untuk mencari tahu sendiri?
Bluu
2
Ya, saya tahu sepertinya situasi rawan kesalahan berasal dari bahasa yang dikompilasi, tetapi Anda hanya harus menerimanya. Dalam prakteknya saya belum menemukan itu menjadi banyak masalah (Ruby dalam kasus saya, bukan Python, tetapi ide yang sama)
Ed S.
Tentu, sudah selesai. Baik jawaban Triptych maupun jawaban mkorpela itu sederhana, saya suka itu, tetapi semangat eksplisit-tersirat yang disebutkan belakangan, dan secara cerdas mencegah kesalahan menang.
Bluu
1
Itu tidak langsung sama, tetapi kelas dasar abstrak memeriksa apakah semua metode abstrak telah ditimpa oleh subkelas. Tentu saja ini tidak membantu jika Anda mengganti metode konkret.
letmaik

Jawaban:

208

Berdasarkan ini dan jawaban fwc: s saya membuat paket yang dapat diinstal pip https://github.com/mkorpela/overrides

Dari waktu ke waktu saya berakhir di sini melihat pertanyaan ini. Terutama ini terjadi setelah (lagi) melihat bug yang sama di basis kode kami: Seseorang telah melupakan beberapa "antarmuka" mengimplementasikan kelas saat mengganti nama metode di "antarmuka" ..

Nah Python bukan Java tetapi Python memiliki kekuatan - dan eksplisit lebih baik daripada implisit - dan ada kasus nyata nyata di dunia nyata di mana hal ini akan membantu saya.

Jadi di sini adalah sketsa dekorator override. Ini akan memeriksa bahwa kelas yang diberikan sebagai parameter memiliki nama metode (atau sesuatu) yang sama dengan metode yang sedang didekorasi.

Jika Anda dapat memikirkan solusi yang lebih baik, silakan posting di sini!

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

Ia bekerja sebagai berikut:

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


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

dan jika Anda melakukan versi yang salah itu akan menimbulkan kesalahan pernyataan selama pemuatan kelas:

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

>> AssertionError!!!!!!!
mkorpela
sumber
18
Luar biasa. Ini menangkap bug yang salah mengeja saat pertama kali saya mencobanya. Pujian.
Christopher Bruns
7
mfbutner: tidak dipanggil setiap kali metode dieksekusi - hanya ketika metode ini dibuat.
mkorpela
3
Ini juga bagus untuk string dokumen! overridesdapat menyalin dokumentasi metode overridden jika metode overriding tidak memiliki metode sendiri.
letmaik
5
@ mkorpela, Heh kode Anda ini harus dalam sistem lib python default. Mengapa Anda tidak menaruh ini di sistem pip? : P
5
@ mkorpela: Oh dan saya sarankan untuk memberi tahu pengembang inti python tentang paket ini, mereka mungkin ingin mempertimbangkan menambahkan tambahan dekorator ke sistem inti python. :)
30

Berikut ini adalah implementasi yang tidak memerlukan spesifikasi nama interface_class.

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
fwc
sumber
2
Sedikit ajaib tetapi membuat penggunaan tipikal jauh lebih mudah. Bisakah Anda memasukkan contoh penggunaan?
Bluu
berapa biaya rata-rata dan kasus terburuk dari menggunakan dekorator ini, mungkin dinyatakan sebagai perbandingan dengan dekorator bawaan seperti @classmethod atau @property?
larham1
4
@ larham1 Dekorator ini dijalankan sekali, ketika definisi kelas dianalisis, bukan pada setiap panggilan. Oleh karena itu biaya eksekusi tidak relevan, jika dibandingkan dengan runtime program.
Abgan
Ini akan jauh lebih baik di Python 3.6 berkat PEP 487 .
Neil G
Untuk mendapatkan pesan kesalahan yang lebih baik: nyatakan setiap (hasattr (cls, method .__ name__) untuk cls di base_classes), 'Overriden method "{}" tidak ditemukan dalam kelas dasar.'. Format (method .__ name__)
Ivan Kovtun
14

Jika Anda menginginkan ini hanya untuk keperluan dokumentasi, Anda dapat menentukan dekorator override Anda sendiri:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

Ini benar-benar tidak lain adalah eye-candy, kecuali jika Anda membuat override (f) sedemikian rupa yang sebenarnya memeriksa override.

Tapi kemudian, ini Python, mengapa menulisnya seperti Java?

Ber
sumber
2
Seseorang dapat menambahkan validasi aktual melalui inspeksi ke overridedekorator.
Erik Kaplun
70
Tapi kemudian, ini Python, mengapa menulisnya seperti Java? Karena beberapa ide di Jawa bagus dan layak diperluas ke bahasa lain?
Piotr Dobrogost
9
Karena ketika Anda mengganti nama metode dalam superclass, akan lebih baik untuk mengetahui bahwa beberapa subclass 2 level bawah menimpanya. Tentu, mudah untuk memeriksa, tetapi sedikit bantuan dari pengurai bahasa tidak akan sakit.
Abgan
4
Karena itu ide yang bagus. Fakta bahwa berbagai bahasa lain memiliki fitur tidak ada argumen - baik untuk atau melawan.
sfkleach
6

Python bukan Java. Tentu saja tidak ada yang benar-benar memeriksa waktu kompilasi.

Saya pikir komentar di docstring banyak. Ini memungkinkan setiap pengguna metode Anda untuk mengetik help(obj.method)dan melihat bahwa metode tersebut merupakan override.

Anda juga dapat memperluas antarmuka secara eksplisit class Foo(Interface), yang akan memungkinkan pengguna mengetik help(Interface.method)untuk mendapatkan ide tentang fungsionalitas yang dimaksudkan metode Anda.

Triptych
sumber
57
Titik sebenarnya @Overridedi Jawa bukan untuk mendokumentasikan - itu untuk menangkap kesalahan ketika Anda bermaksud mengganti metode, tetapi akhirnya mendefinisikan yang baru (misalnya karena Anda salah mengeja nama; di Jawa, itu mungkin juga terjadi karena Anda menggunakan tanda tangan yang salah, tapi ini bukan masalah dengan Python - tetapi kesalahan ejaan masih).
Pavel Minaev
2
@ Pavel Minaev: Benar, tetapi masih nyaman untuk memiliki dokumentasi, terutama jika Anda menggunakan IDE / editor teks yang tidak memiliki indikator otomatis untuk penggantian (Eclipse's JDT menunjukkannya dengan rapi di samping nomor baris, misalnya).
Tuukka Mustonen
2
@PavelMinaev Salah Salah satu poin utama @Overrideadalah dokumentasi selain kompilasi pengecekan waktu.
siamii
6
@siamii Saya pikir bantuan untuk dokumentasi sangat bagus, tetapi di semua dokumentasi resmi Java yang saya lihat, mereka hanya menunjukkan pentingnya waktu kompilasi cek. Harap membuktikan klaim Anda bahwa Pavel "salah."
Andrew Mellinger
5

Berimprovisasi pada @ mkorpela jawaban yang bagus , berikut adalah versi dengan

pemeriksaan yang lebih tepat, penamaan, dan mengangkat objek Kesalahan

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


Berikut ini tampilannya dalam praktiknya:

NotImplementedError" tidak diterapkan di kelas dasar "

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

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

menghasilkan NotImplementedErrorkesalahan yang lebih deskriptif

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

tumpukan penuh

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" tipe yang diimplementasikan yang diharapkan "

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

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

menghasilkan NotImplementedErrorkesalahan yang lebih deskriptif

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

tumpukan penuh

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'>




Hebatnya tentang jawaban @ mkorpela adalah cek terjadi selama beberapa tahap inisialisasi. Cek tidak perlu "dijalankan". Mengacu pada contoh sebelumnya, class Btidak pernah diinisialisasi ( B()) namun NotImplementedErrormasih akan meningkat. Ini berarti overrideskesalahan ditangkap lebih cepat.

JamesThomasMoon1979
sumber
Hei! Ini terlihat menarik. Bisakah Anda mempertimbangkan untuk melakukan permintaan tarik pada proyek ipromise saya? Saya telah menambahkan jawaban.
Neil G
@ NeilG Saya bercabang proyek ipromise dan sedikit kode. Sepertinya pada dasarnya Anda sudah menerapkan ini di dalam overrides.py. Saya tidak yakin apa lagi yang dapat saya tingkatkan secara signifikan kecuali untuk mengubah jenis pengecualian dari TypeErrormenjadi NotImplementedError.
JamesThomasMoon1979
Hei! Terima kasih, saya tidak memiliki pemeriksaan bahwa objek yang ditimpa sebenarnya memiliki tipe types.MethodType. Itu ide yang bagus dalam jawaban Anda.
Neil G
2

Seperti orang lain katakan tidak seperti Java tidak ada tag @Overide namun di atas Anda dapat membuat dekorator Anda sendiri tetapi saya sarankan menggunakan metode getattrib () global daripada menggunakan dict internal sehingga Anda mendapatkan sesuatu seperti berikut:

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

Jika Anda ingin, Anda bisa menangkap getattr () di tangkapan Anda sendiri, coba naikkan kesalahan Anda sendiri, tetapi saya pikir metode getattr lebih baik dalam kasus ini.

Ini juga menangkap semua item yang terikat ke kelas termasuk metode kelas dan vairables

Bicker x 2
sumber
2

Berdasarkan jawaban luar biasa @ mkorpela, saya telah menulis paket serupa ( ipromise pypi github ) yang melakukan lebih banyak pemeriksaan:

Misalkan Amewarisi dari Bdan C, Bmewarisi dari C.

Modul ipromise memeriksa bahwa:

  • Jika A.fmengganti B.f, B.fharus ada, dan Aharus mewarisi dari B. (Ini adalah cek dari paket override).

  • Anda tidak memiliki pola yang A.fmenyatakan bahwa itu menimpa B.f, yang kemudian menyatakan bahwa ia menimpa C.f. Aharus mengatakan bahwa itu menimpa C.fkarena Bmungkin memutuskan untuk berhenti mengganti metode ini, dan itu tidak akan menghasilkan pembaruan hilir.

  • Anda tidak memiliki pola yang A.fmenyatakan bahwa itu menimpa C.f, tetapi B.ftidak menyatakan penimpaannya.

  • Anda tidak memiliki pola A.fmenyatakan bahwa itu menimpa C.f, tetapi B.fmenyatakan bahwa itu menimpa dari beberapa D.f.

Ini juga memiliki berbagai fitur untuk menandai dan memeriksa penerapan metode abstrak.

Neil G
sumber
0

Dengar paling sederhana dan bekerja di bawah Jython dengan kelas Java:

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()
pengguna3034016
sumber
0

Tidak hanya dekorator yang saya buat memeriksa apakah nama atribut utama dalam adalah superclass dari kelas atribut dalam tanpa harus menentukan superclass, dekorator ini juga memeriksa untuk memastikan atribut utama harus sama dengan yang ditimpa. atribut. Metode Kelas diperlakukan seperti metode dan Metode Statis diperlakukan seperti fungsi. Dekorator ini berfungsi untuk callable, metode kelas, metode statis, dan properti.

Untuk kode sumber lihat: https://github.com/fireuser909/override

Dekorator ini hanya berfungsi untuk kelas yang merupakan instance dari override.OverridesMeta tetapi jika kelas Anda adalah instance dari metaclass khusus, gunakan fungsi create_custom_overrides_meta untuk membuat metaclass yang kompatibel dengan dekorator override. Untuk tes, jalankan modul override .__ init__.

Michael
sumber
0

Dalam Python 2.6+ dan Python 3.2+ Anda dapat melakukannya ( Sebenarnya mensimulasikannya , Python tidak mendukung fungsi overloading dan kelas anak secara otomatis menimpa metode induk). Kita dapat menggunakan Dekorator untuk ini. Tetapi pertama-tama, perhatikan bahwa Python @decoratorsdan Java @Annotationsadalah hal yang sama sekali berbeda. Yang sebelumnya adalah pembungkus dengan kode konkret sementara yang lain adalah flag ke compiler.

Untuk ini, pertama lakukan 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)

keluaran:

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

Perhatikan bahwa Anda harus menggunakan dekorator di sini dengan tanda kurung

Satu hal yang perlu diingat adalah bahwa karena Python tidak memiliki fungsi overloading secara langsung, jadi meskipun Kelas B tidak mewarisi dari Kelas A tetapi membutuhkan semua fooitu selain Anda juga perlu menggunakan @Override (meskipun menggunakan alias 'Kelebihan' akan terlihat lebih baik dalam hal itu)

mradul
sumber