94176


Pythonで、オーバーライドしたメソッドに、明示的に印をつけることはできないでしょうか？  
（Javaの@overrideアノテーションのようなもの、Pythonにはないでしょうか？）

[code]

    from abc import ABC, abstractmethod
    
    class BaseClass(ABC):
        @abstractmethod
        def my_method(self):
            """This is an abstract method."""
            pass
    
    class DerivedClass(BaseClass):
    #   @override ←こんな感じの印(デコレータ)はつけられないですよね？
        def my_method(self):
            """Override the abstract method to do something else."""
            # ↑かわりにDocStringに記載するのが一般的でしょうか？どんな文言にしていますか？
            print("Hello, world!")
    
[/code]


CC BY-SA 4.0
2023-03-14T14:51:46.573
94175
2023-05-11T02:34:38.700
35267
question
python python3
Pythonのオーバーライドしたメソッドに印をつける方法


> `@override` ←こんな感じの印(デコレータ)はつけられないですよね？

適当に `override decorator` を作ってしまえばよいでしょう。

[code]

    from abc import ABC, abstractmethod
    import inspect
    
    class BaseClass(ABC):
        @abstractmethod
        def my_method(self):
            """This is an abstract method."""
            pass
    
    def override(method):
        def _override(*args, **kwargs):
            cls = getattr(inspect.getmodule(method), method.__qualname__.split('.', 1)[0], None)
            assert(any(method.__name__ in dir(c) for c in cls.__bases__))
            method(*args, **kwargs)
        return _override
    
    class DerivedClass(BaseClass):
        @override
        def my_method(self):
            """Override the abstract method to do something else."""
            print("Hello, world!")
    
    if __name__ == '__main__':
        DerivedClass().my_method()
    
[/code]


CC BY-SA 4.0
2023-03-14T18:00:35.477
94176
2023-03-14T18:00:35.477
47127
94175
answer


[a.]@wrapsデコレータを追加しました。  
[b.]戻り値を戻すようにしました。  
[c.]型チェックを入れました。

以下のような感じになりました。

[code]

    from functools import wraps
    
    def override(method):
        """オーバーライドしているかチェックする。"""
        # [a.]
        @wraps(method)
        def _override(*args, **kwargs):
            # [c.]
            assert hasattr(method, '__qualname__'), f'Type Error.'
            cls = getattr(inspect.getmodule(method), method.__qualname__.split('.', 1)[0], None)
            assert any(method.__name__ in dir(c) for c in cls.__bases__), f'Not overridden. Method:[{method.__name__}]'
            # [b.]
            return method(*args, **kwargs)
        return _override
    
[/code]


CC BY-SA 4.0
2023-03-15T15:31:04.460
94195
2023-03-15T15:31:04.460
35267
94175
answer


【参考】  
クラスの中に、クラスを定義し、継承関係がある場合でも、動作するようにしました。  
（以前の回答例では、ネストしたクラスが含まれていると動作しませんでした。）

[code]

    from typing import Any
    from typing import Callable
    import functools
    import inspect
    
    
    def get_attribute(obj, attribute_text: str, delimiter: str = '.') -> Any:
        """区切り文字で区切られた文字列を分割し、対応するattributeを取得する。"""
        attributes = attribute_text.split(delimiter)
        for attr in attributes:
            if not hasattr(obj, attr):
                return None
            obj = getattr(obj, attr)
        return obj
    
    
    def get_class(func: Callable) -> Any:
        """指定したメソッドが所属しているクラスを取得する"""
        mod = inspect.getmodule(func)
        attribute_text = func.__qualname__.rsplit('.', 1)[0]  # RootClass.ChildClass.method
        return get_attribute(mod, attribute_text)
    
    
    def is_override(func: Callable) -> bool:
        """Overrideされているか判定する"""
        assert hasattr(func, '__qualname__'), f'Type Error.'
        cls = get_class(func)
        assert cls is not None, 'cls is None.'
        # 複数の親クラスのそれぞれにdirを実行し、同名のメソッド・属性がないか判定する。
        for base_class in cls.__bases__:
            if func.__name__ in dir(base_class):
                return True
        return False
    
    
    # デコレータ
    def override(func: Callable) -> Callable:
        """Overrideされているか判定する"""
        checked = False  # 多重チェック防止フラグ
    
        @functools.wraps(func)
        def decorator(*args, **kwargs):
            nonlocal checked
            if not checked:
                assert is_override(func), f'Not overridden. Method:[{func.__name__}]'
                checked = True
            return func(*args, **kwargs)
        return decorator
    
[/code]

以下、利用例です。

[code]

    ################################################################################
    # テスト用クラス
    class Root():
        class Base():
            def print(self):
                print('BaseClass')
    
        class Child(Base):
            @override
            def print(self):
                print('SubClass')
    
    
    ################################################################################
    if __name__ == '__main__':
        test = Root.Child()
        test.print()
    
    
[/code]


CC BY-SA 4.0
2023-05-11T02:34:38.700
94809
2023-05-11T02:34:38.700
53109
94175
answer