ใน Python ฉันจะระบุได้อย่างไรว่าฉันเอาชนะวิธีการได้?


173

ตัวอย่างเช่นใน Java @Overrideคำอธิบายประกอบไม่เพียง แต่ให้การตรวจสอบเวลาการคอมไพล์ของการแทนที่ แต่ยังสร้างรหัสการทำเอกสารด้วยตนเองที่ยอดเยี่ยม

ฉันแค่มองหาเอกสาร (แม้ว่าจะเป็นตัวบ่งชี้ถึงตัวตรวจสอบบางอย่างเช่นไพลินต์นั่นเป็นโบนัส) ฉันสามารถเพิ่มความคิดเห็นหรือ docstring ที่ไหนสักแห่ง แต่วิธีการที่บ่งบอกถึงการแทนที่ใน Python คืออะไร?


13
ในคำอื่น ๆ คุณไม่เคยระบุว่าคุณกำลังเอาชนะวิธี? ปล่อยให้ผู้อ่านคิดออกเองเหรอ?
Bluu

2
ใช่ฉันรู้ว่าดูเหมือนว่ามีแนวโน้มผิดพลาดเกิดขึ้นจากภาษาที่คอมไพล์ แต่คุณต้องยอมรับมัน ในทางปฏิบัติฉันไม่พบว่ามันเป็นปัญหามาก (Ruby ในกรณีของฉันไม่ใช่ Python แต่เป็นความคิดเดียวกัน)
35499 Ed S.

แน่นอนว่าทำ ทั้งคำตอบของ Triptych และคำตอบของ mkorpela นั้นเรียบง่ายฉันชอบมัน แต่วิญญาณหลังชัดแจ้งโดยปริยายและป้องกันข้อผิดพลาดอย่างชาญฉลาด
Bluu

1
มันไม่เหมือนกันโดยตรง แต่คลาสพื้นฐานที่เป็นนามธรรมจะตรวจสอบว่าเมธอดนามธรรมทั้งหมดถูกแทนที่โดยคลาสย่อยหรือไม่ ของหลักสูตรนี้ไม่ได้ช่วยถ้าคุณเอาชนะวิธีที่เป็นรูปธรรม
letmaik

คำตอบ:


208

จากคำตอบนี้และคำตอบ fwc: ฉันสร้างแพ็คเกจที่สามารถติดตั้ง pip ได้ที่https://github.com/mkorpela/overrides

บางครั้งฉันก็จบที่นี่ดูคำถามนี้ ส่วนใหญ่เกิดขึ้นหลังจาก (อีกครั้ง) เห็นข้อผิดพลาดเดียวกันในฐานรหัสของเรา: บางคนได้ลืมบางส่วน "อินเทอร์เฟซ" การใช้คลาสในขณะที่เปลี่ยนชื่อวิธีใน "อินเทอร์เฟซ" ..

Python ไม่ใช่ Java แต่ Python มีพลัง - และชัดเจนดีกว่าโดยนัย - และมีกรณีที่เป็นรูปธรรมที่แท้จริงในโลกแห่งความจริงที่สิ่งนี้จะช่วยฉันได้

ดังนั้นนี่คือภาพร่างของมัณฑนากรแทนที่ สิ่งนี้จะตรวจสอบว่าคลาสที่กำหนดเป็นพารามิเตอร์มีชื่อวิธีการ (หรือบางอย่าง) เหมือนกับวิธีการตกแต่ง

หากคุณนึกถึงทางออกที่ดีกว่าได้โปรดโพสต์ไว้ที่นี่!

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

มันทำงานได้ดังต่อไปนี้:

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


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

และถ้าคุณทำเวอร์ชันที่ผิดพลาดมันจะเพิ่มข้อผิดพลาดในการยืนยันระหว่างการโหลดคลาส:

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

>> AssertionError!!!!!!!

18
น่ากลัว นี่เป็นข้อผิดพลาดที่สะกดผิดในครั้งแรกที่ฉันลอง ความรุ่งโรจน์
Christopher Bruns

7
mfbutner: มันไม่ได้ถูกเรียกทุกครั้งที่มีการเรียกใช้เมธอด - เฉพาะเมื่อสร้างเมธอด
mkorpela

3
นี่เป็นสิ่งที่ดีสำหรับสตริง doc! overridesสามารถคัดลอก docstring ของวิธีการแทนที่หากวิธีการแทนที่ไม่มีวิธีการอย่างใดอย่างหนึ่ง
letmaik

5
@mkorpela, Heh รหัสของคุณควรจะอยู่ในระบบ lib เริ่มต้นหลาม ทำไมคุณไม่ใส่สิ่งนี้ลงในระบบ pip? : P

5
@mkorpela: โอ้และฉันขอแนะนำให้ผู้พัฒนา python ทราบเกี่ยวกับแพคเกจนี้พวกเขาอาจต้องการพิจารณาการเพิ่ม obout overide decorator ในระบบ core python :)

30

นี่คือการใช้งานที่ไม่ต้องการข้อมูลจำเพาะของชื่อ 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

2
ขลังเล็กน้อย แต่ใช้งานทั่วไปได้ง่ายขึ้นมาก คุณสามารถใส่ตัวอย่างการใช้งานได้ไหม
Bluu

ค่าใช้จ่ายเฉลี่ยและที่แย่ที่สุดในการใช้มัณฑนากรนี้อาจแสดงเป็นการเปรียบเทียบกับมัณฑนากร build-in เช่น @classmethod หรือ @property
larham1

4
@ larham1 เครื่องมือตกแต่งนี้จะทำงานหนึ่งครั้งเมื่อมีการวิเคราะห์คำจำกัดความของคลาสไม่ใช่การโทรแต่ละครั้ง ดังนั้นจึงไม่มีค่าใช้จ่ายในการดำเนินการเมื่อเทียบกับรันไทม์ของโปรแกรม
Abgan

นี้จะเป็นมาก nicer ในหลาม 3.6 ขอบคุณที่PEP 487
Neil G

ในการรับข้อความแสดงข้อผิดพลาดที่ดีขึ้น: ยืนยันใด ๆ (hasattr (cls, method .__ name__) สำหรับ cls ใน base_classes), 'Overriden method "{}" ไม่พบในคลาสพื้นฐาน'. format (method .__ name__)
Ivan Kovtun

14

หากคุณต้องการสิ่งนี้เพื่อวัตถุประสงค์ด้านเอกสารเท่านั้นคุณสามารถกำหนดมัณฑนากรแทนที่ของคุณเอง:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

นี่ไม่ใช่สิ่งอื่นนอกจากอาย - อายเว้นแต่คุณจะสร้าง override (f) ในลักษณะที่ตรวจสอบการแทนที่จริง ๆ

แต่นี่คือ Python ทำไมจึงเขียนเหมือน Java


2
หนึ่งสามารถเพิ่มการตรวจสอบที่แท้จริงผ่านการตรวจสอบเพื่อoverrideมัณฑนากร
Erik Kaplun

70
แต่นี่คือ Python ทำไมจึงเขียนเหมือน Java เพราะความคิดบางอย่างใน Java นั้นดีและคุ้มค่าที่จะขยายไปสู่ภาษาอื่น ๆ ?
Piotr Dobrogost

9
เพราะเมื่อคุณเปลี่ยนชื่อเมธอดในซูเปอร์คลาสมันก็เป็นการดีที่ได้รู้ว่าคลาสย่อย 2 ระดับบางอันลงมาแทนที่มัน แน่นอนมันง่ายต่อการตรวจสอบ แต่ความช่วยเหลือเล็กน้อยจากตัวแยกวิเคราะห์ภาษาจะไม่เจ็บ
Abgan

4
เพราะมันเป็นความคิดที่ดี ความจริงที่ว่าภาษาอื่น ๆ ที่หลากหลายนั้นมีคุณสมบัติไม่ใช่ข้อโต้แย้งไม่ว่าจะเพื่อหรือต่อต้าน
sfkleach

6

Python ไม่ใช่ Java มีแน่นอนไม่มีสิ่งเช่นการตรวจสอบรวบรวมเวลาจริง ๆ

ฉันคิดว่าความคิดเห็นใน docstring มีมากมาย สิ่งนี้อนุญาตให้ผู้ใช้วิธีการของคุณพิมพ์help(obj.method)และดูว่าวิธีนั้นเป็นวิธีการแทนที่

คุณยังสามารถขยายส่วนต่อประสานอย่างชัดเจนด้วยclass Foo(Interface)ซึ่งจะช่วยให้ผู้ใช้พิมพ์help(Interface.method)เพื่อรับแนวคิดเกี่ยวกับฟังก์ชันการทำงานที่วิธีการของคุณตั้งใจจะให้


57
จุดที่แท้จริงของ@OverrideJava ไม่ได้อยู่ที่เอกสาร แต่เป็นการจับข้อผิดพลาดเมื่อคุณต้องการแทนที่เมธอด แต่จบลงด้วยการนิยามใหม่ (เช่นเนื่องจากคุณสะกดชื่อผิดใน Java มันอาจเกิดขึ้นเพราะคุณใช้ ลายเซ็นไม่ถูกต้อง แต่นี่ไม่ใช่ปัญหาใน Python - แต่ยังสะกดผิดอยู่)
Pavel Minaev

2
@ Pavel Minaev: จริง แต่ก็ยังสะดวกสำหรับเอกสารโดยเฉพาะอย่างยิ่งถ้าคุณใช้ IDE / text editor ซึ่งไม่มีตัวบ่งชี้อัตโนมัติสำหรับการแทนที่ (ตัวอย่างเช่น JDT ของ Eclipse แสดงหมายเลขข้างๆอย่างเป็นระเบียบ)
Tuukka Mustonen

2
@PavelMinaev ผิด หนึ่งในประเด็นหลักของ@Overrideเอกสารประกอบนอกเหนือจากการตรวจสอบเวลารวบรวม
siamii

6
@siamii ฉันคิดว่าการช่วยเหลือเอกสารเป็นสิ่งที่ดี แต่ในเอกสาร Java อย่างเป็นทางการทั้งหมดที่ฉันเห็นพวกเขาเพียงระบุความสำคัญของการตรวจสอบเวลารวบรวม โปรดยืนยันการอ้างสิทธิ์ของคุณว่า Pavel นั้น "ผิด"
Andrew Mellinger

5

ทันควันกับ @mkorpela คำตอบที่ดีนี่คือรุ่นที่มี

การตรวจสอบการตั้งชื่อและวัตถุข้อผิดพลาดที่แม่นยำยิ่งขึ้น

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


นี่คือสิ่งที่ดูเหมือนว่าในทางปฏิบัติ:

NotImplementedError" ไม่ได้ใช้งานในคลาสฐาน "

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

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

ส่งผลให้เกิดNotImplementedErrorข้อผิดพลาดที่อธิบายเพิ่มเติม

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

เต็มสแต็ค

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" ประเภทการใช้งานที่คาดหมาย "

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

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

ส่งผลให้เกิด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'>

เต็มสแต็ค

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 คือการตรวจสอบเกิดขึ้นในช่วงเริ่มต้น การตรวจสอบไม่จำเป็นต้องเป็น "เรียกใช้" อ้างถึงตัวอย่างก่อนหน้าclass Bนี้จะไม่เริ่มต้น ( B()) ยังNotImplementedErrorจะเพิ่มขึ้น ซึ่งหมายความว่าoverridesจะพบข้อผิดพลาดได้เร็วขึ้น


เฮ้! มันดูน่าสนใจ คุณสามารถพิจารณาทำคำขอดึงในโครงการ ipromise ของฉัน ฉันได้เพิ่มคำตอบแล้ว
Neil G

@ NeilG ฉันแยกโครงการipromiseและเขียนรหัสเล็กน้อย overrides.pyดูเหมือนว่าคุณได้ใช้หลักนี้ภายใน ผมไม่แน่ใจว่าอะไรที่ฉันอย่างมีนัยสำคัญสามารถปรับปรุงการยกเว้นการเปลี่ยนประเภทการยกเว้นจากการTypeError NotImplementedError
JamesThomasMoon1979

เฮ้! types.MethodTypeขอบคุณฉันไม่ได้มีการตรวจสอบว่าวัตถุแทนที่จริงมีประเภท นั่นเป็นความคิดที่ดีในคำตอบของคุณ
Neil G

2

เช่นเดียวกับที่คนอื่นพูดไม่เหมือนกับ Java ไม่มีแท็ก @Overide แต่เหนือคุณสามารถสร้างของคุณเองโดยใช้มัณฑนากร แต่ผมขอแนะนำให้ใช้ getattrib () วิธีการทั่วโลกแทนการใช้ dict ภายในเพื่อให้คุณได้รับสิ่งต่อไปนี้:

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

หากคุณต้องการคุณสามารถจับ getattr () ในลองของคุณเองยกข้อผิดพลาดของคุณเอง แต่ฉันคิดว่าวิธี getattr ดีกว่าในกรณีนี้

นอกจากนี้ยังจับรายการทั้งหมดที่ถูกผูกไว้กับคลาสรวมถึงวิธีการเรียนและ vairables


2

จากคำตอบที่ยอดเยี่ยมของ @ mkorpela ฉันได้เขียนแพ็คเกจที่คล้ายกัน ( ipromise pypi github ) ซึ่งทำการตรวจสอบอื่น ๆ อีกมากมาย:

สมมติว่าAสืบทอดมาจากBและC, สืบทอดจากBC

โมดูลipromiseตรวจสอบว่า:

  • หากA.fแทนที่B.f, B.fต้องมีอยู่และจะต้องได้รับมรดกจากA B(นี่คือการตรวจสอบจากแพ็คเกจการแทนที่)

  • คุณไม่ได้มีรูปแบบA.fประกาศว่าแทนที่ที่แล้วบอกว่ามันจะแทนที่B.f ควรบอกว่ามันแทนที่ตั้งแต่ตั้งแต่อาจตัดสินใจที่จะหยุดการเอาชนะวิธีนี้และไม่ควรส่งผลให้การปรับปรุงดาวน์สตรีมC.fAC.fB

  • คุณไม่มีรูปแบบที่A.fประกาศว่ามีการแทนที่C.fแต่B.fไม่ได้ประกาศการแทนที่ของมัน

  • คุณไม่ได้มีรูปแบบA.fประกาศว่าแทนที่C.fแต่บอกว่ามันจะแทนที่จากบางส่วนB.fD.f

นอกจากนี้ยังมีคุณสมบัติต่าง ๆ สำหรับการทำเครื่องหมายและตรวจสอบการใช้วิธีนามธรรม


0

Hear ง่ายที่สุดและทำงานภายใต้ Jython ด้วยคลาส 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()

0

มัณฑนากรไม่เพียง แต่ทำการตรวจสอบว่าชื่อของคุณลักษณะการแทนที่ในนั้นเป็น superclass ใด ๆ ของคลาสที่มีแอตทริบิวต์อยู่โดยไม่ต้องระบุ superclass มัณฑนากรนี้ยังตรวจสอบเพื่อให้แน่ใจว่าแอตทริบิวต์การแทนที่จะต้องเป็นชนิดเดียวกันกับการแทนที่ คุณลักษณะ วิธีการเรียนจะได้รับการปฏิบัติเหมือนวิธีการและวิธีการคงที่จะได้รับการปฏิบัติเหมือนฟังก์ชั่น มัณฑนากรนี้ทำงานสำหรับ callables, วิธีการเรียน, วิธีการคงที่และคุณสมบัติ

ดูซอร์สโค้ดได้ที่: https://github.com/fireuser909/override

มัณฑนากรนี้ทำงานเฉพาะคลาสที่เป็นอินสแตนซ์ของ override.OverridesMeta แต่ถ้าคลาสของคุณเป็นอินสแตนซ์ของ metaclass ที่กำหนดเองให้ใช้ฟังก์ชัน create_custom_overrides_meta เพื่อสร้างเมตากลาสที่เข้ากันได้กับ decorator ของ override สำหรับการทดสอบให้รันโมดูลแทนที่. _ init__


0

ใน Python 2.6+ และ Python 3.2+ คุณสามารถทำได้ ( จริง ๆ แล้วจำลอง Python ไม่รองรับฟังก์ชั่นการโอเวอร์โหลดและคลาสย่อยจะแทนที่เมธอดของพาเรนต์โดยอัตโนมัติ) เราสามารถใช้ Decorators สำหรับสิ่งนี้ แต่ก่อนอื่นให้สังเกตว่า Python @decoratorsและ Java @Annotationsเป็นสิ่งที่แตกต่างกันโดยสิ้นเชิง ก่อนหน้านี้เป็นเสื้อคลุมที่มีรหัสคอนกรีตในขณะที่หนึ่งในภายหลังคือการตั้งค่าสถานะเพื่อคอมไพเลอร์

สำหรับสิ่งนี้ทำก่อน 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)

เอาท์พุท:

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

โปรดทราบว่าคุณต้องใช้มัณฑนากรที่นี่พร้อมกับวงเล็บ

สิ่งหนึ่งที่ต้องจำคือเนื่องจาก Python ไม่มีฟังก์ชั่นโอเวอร์โหลดโดยตรงดังนั้นแม้ว่า Class B จะไม่ได้รับสืบทอดจาก Class A แต่ต้องการทั้งหมดที่fooมากกว่าคุณต้องใช้ @Override (แม้ว่าการใช้นามแฝง 'Overload' จะดู ดีกว่าในกรณีนั้น)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.