Das Unterdrücken von Methodenüberschreibungen in Python

Ich habe es zuerst in 3 gepostet, aber ich habe es verschoben, weil der gestrige Teil von 1 nicht gepostet und frei war.

Das Verbot der Methodenüberschreibung während der Vererbung kann beispielsweise mit dem Schlüsselwort final in Java und C ++ erreicht werden, ist jedoch keine Standardfunktion von Python. Also habe ich recherchiert und darüber nachgedacht, wie es in Python3 geht.

Umgebung

Implementierung

Die gesamte Implementierung befindet sich unter https://github.com/eduidl/python-sandbox/tree/master/prohibit_override.

mypy

Typhinweise wurden aus Python 3.5 eingeführt, es ist jedoch ein Tool zum statischen Überprüfen von Typen mithilfe der Typhinweise. Es ist so mypy, aber Sie können den Dekorator @ final verwenden, um zu deklarieren, dass es eine Methode ist, die nicht überschrieben werden sollte.

Die Verwendung ist wie folgt. Wir verwenden "@ final", um zu erklären, dass es sich um eine endgültige Methode namens "Hallo" handelt.

mypy.py


from typing_extensions import final

class Base:
    @final
    def hello(self) -> None:
        print("hello")

class Derived(Base):
    def hello(self) -> None:
        print("Hallo")

Base().hello()
Derived().hello()

Wenn Sie mypy für diese Datei verwenden, werden Sie gewarnt.

$ mypy mypy.py
mypy.py:13: error: Cannot override final attribute "hello" (previously declared in base class "Base")
Found 1 error in 1 file (checked 1 source file)

Wie bei Type Hints gibt es jedoch zur Laufzeit keine Auswirkungen. Seien Sie also vorsichtig.

$ python3 mypy.py
hello
Hallo

Pros

Cons

Referenz

Nachtrag

Seit Python 3.8 scheint es auch im Typing-Modul der Standardbibliothek implementiert zu sein. Das heißt, Sie benötigen noch eine Typprüfung, einschließlich mypy.

https://docs.python.org/ja/3.8/library/typing.html#typing.final

Verwendung von __init_subclass__

Die statische Analyse von mypy ist gut, aber ich bin froh, wenn ich sie zur Laufzeit überprüfe und eine Ausnahme auslöse. Als Bild sieht es aus wie "abc" (https://docs.python.org/ja/3/library/abc.html), ein abstraktes Basisklassenmodul der Standardbibliothek.

Also habe ich versucht, es unter Bezugnahme auf https://github.com/python/cpython/blob/3.8/Lib/abc.py zu implementieren (auch wenn dort Verweis steht, bleibt nur "isfinalmethod" übrig. Ich fühle mich so). Es ist eine einfache Implementierung, die alle Methoden mit der Basisklasse vergleicht.

import inspect
from typing import Any, Callable, List, Tuple

AnyCallable = Callable[..., Any]


def final(funcobj: AnyCallable) -> AnyCallable:
    setattr(funcobj, '__isfinalmethod__', True)
    return funcobj


def get_func_type(cls: type, func_name: str) -> str:
    func = getattr(cls, func_name)

    if isinstance(func, classmethod):
        return 'class method'
    elif isinstance(func, staticmethod):
        return 'static method'
    else:
        return 'member function'


class Final:

    def __init_subclass__(cls, **kwargs) -> None:
        for func_name, func in cls.get_methods():
            for ancestor in cls.__bases__:
                if ancestor == object or not hasattr(cls, func_name):
                    continue
                ancestor_func = getattr(ancestor, func_name, None)
                if not ancestor_func or not getattr(ancestor_func, '__isfinalmethod__', False) or \
                        type(func) == type(ancestor_func) and \
                        getattr(func, '__func__', func) == getattr(ancestor_func, '__func__', ancestor_func):
                    continue

                func_type = get_func_type(ancestor, func_name)
                raise TypeError(f'Fail to declare class {cls.__name__}, for override final {func_type}: {func_name}')

    @classmethod
    def get_methods(cls) -> List[Tuple[str, AnyCallable]]:
        return inspect.getmembers(cls, lambda x: inspect.isfunction(x) or inspect.ismethod(x))

Implementierungsdetails

Normalerweise überspringe ich es, aber da es sich um einen Adventskalender handelt, werde ich es kurz erklären.

final

def final(funcobj: AnyCallable) -> AnyCallable:
    setattr(funcobj, '__isfinalmethod__', True)
    return funcobj

Die Implementierung von abc.abstractmethod (https://github.com/python/cpython/blob/3.8/Lib/abc.py#L7-L25) ist genau richtig. Weil PyCharm eine Warnung ausspuckt. Ich habe "setattr" verwendet.

get_func_type

def get_func_type(cls: type, func_name: str) -> str:
    func = getattr(cls, func_name)

    if isinstance(func, classmethod):
        return 'class method'
    elif isinstance(func, staticmethod):
        return 'static method'
    else:
        return 'member function'

Ich suche nur nach einer statischen Methode, einer Klassenmethode oder einer Mitgliedsfunktion, die in der Fehlermeldung verwendet werden kann.

Final

Zuerst schrieb ich mit Metaclass, erinnerte mich aber an "Effective Python" Punkt 33 in Python 3.6 wie folgt. Umgeschrieben mit __init_subclass__ (sicherlich einfach zu bedienen).

Final.get_methods

@classmethod
def get_methods(cls) -> List[Tuple[str, AnyCallable]]:
    return inspect.getmembers(cls, lambda x: inspect.isfunction(x) or inspect.ismethod(x))

inspect.getmembers (https://docs.python.org/ja/3/library/inspect.html#inspect.getmembers) verwendet ein Präfix als zweites Argument und gibt zurück, was wahr ist. .. Dieses Mal möchte ich alle Elementfunktionen, statische Methoden und Klassenmethoden, also werde ich diejenigen sammeln, für die "inspect.isfunction" oder "inspect.ismethod" wahr ist.

Final.__init_subclass__

In Bezug auf __init_subclass__ handelt es sich zunächst um eine in Python 3.6 eingeführte Funktion. Einzelheiten finden Sie im offiziellen Dokument (https://docs.python.org/ja/3/reference/datamodel.html#object.init_subclass). Ich werde zitieren.

Diese Methode wird immer dann aufgerufen, wenn die Klasse, in der sie definiert ist, vererbt wird. cls ist eine neue Unterklasse.

Das ist alles, aber kurz gesagt, es ist einfacher zu schreiben als früher mit Metaklassen.

# Python 3.Bis zu 5
#Eigentlich benutze ich nur Exampale, nicht direkt Exampale Meta
class ExampaleMeta(type):
    def __new__(mcs, name, bases, attrs):
        cls = super().__new__(mcs, name, bases, attrs)
        some_func(cls)
        return cls

class Exampale(metaclass=ExampaleMeta):
    def __init__(self, args):
        #verschiedene

# Python 3.Ab 6
class Exampale:
    def __init__(self, args):
        #verschiedene

    def __init_subclass__(cls, **kwargs):
        some_func(cls)

Wenn Sie also Python 3.6 verwenden können, sollten Sie "init_subclass" so oft wie möglich verwenden. Also, über diese Implementierung.

def __init_subclass__(cls, **kwargs) -> None:
    for func_name, func in cls.get_methods():
        for ancestor in cls.__bases__:
            if ancestor == object or not hasattr(cls, func_name):
                continue
            ancestor_func = getattr(ancestor, func_name, None)
            if not ancestor_func or not getattr(ancestor_func, '__isfinalmethod__', False) or \
                    type(func) == type(ancestor_func) and \
                    getattr(func, '__func__', func) == getattr(ancestor_func, '__func__', ancestor_func):
                continue

            func_type = get_func_type(ancestor, func_name)
            raise TypeError(f'Fail to declare class {cls.__name__}, for override final {func_type}: {func_name}')

Holen Sie sich alle Methoden mit Final.get_methods, erhalten Sie geerbte Klassen mit __bases__ und für jede Klasse

Überprüfen Sie und erhöhen Sie gegebenenfalls den Typfehler. Übrigens wirft abc auch TypeError.

Anwendungsbeispiel

Bereiten Sie die folgende Klasse vor.

class A(metaclass=FinalMeta):

    @final
    def final_member(self):
        pass

    @classmethod
    @final
    def final_class(cls):
        pass

    @staticmethod
    @final
    def final_static():
        pass

    def overridable(self):
        print("from A")


class B(A):
    pass

Überschreiben der Mitgliedsfunktion

Ich habe ein paar Fälle ausprobiert und es scheint zu funktionieren.

class C(A):

    def final_member(self) -> None:
        pass
#=> Fail to declare class C, for override final member function: final_member
class D(B):

    def final_member(self) -> None:
        pass
#=> Fail to declare class D, for override final member function: final_member
class E(A, int):

    def final_member(self) -> None:
        pass
#=> Fail to declare class E, for override final member function: final_member

class F(int, B):

    def final_member(self) -> None:
        pass
#=> Fail to declare class F, for override final member function: final_member
class G(A):

    @classmethod
    def final_member(cls) -> None:
        pass
#=> Fail to declare class G, for override final member function: final_member
class H(A):

    @staticmethod
    def final_member() -> None:
        pass
#=> Fail to declare class H, for override final member function: final_member

Klassenmethode überschreiben

Nur ein Fall.

class J(A):

    @classmethod
    def final_class(cls) -> None:
        pass
#=> Fail to declare class J, for override final class method: final_class

statische Methodenüberschreibung

Dies ist auch nur ein Fall.

class K(A):

    @staticmethod
    def final_static() -> None:
        pass
#=> Fail to declare class K, for override final static method: final_static

normal

Schauen wir uns zum Schluss den Fall an, in dem keine Ausnahme auftritt. Klingt okay.

class L(A):

    def overridable(self) -> None:
        print("from L")


L().overridable()
#=> from l

Pros

--Kann Überschreibungen erkennen und zur Laufzeit Ausnahmen auslösen

Cons

Ergänzung

Im Fall von "abc" wird zum Zeitpunkt der Instanziierung eine Ausnahme ausgelöst, aber ich denke, dies liegt daran, dass Sie keine abstrakte Klasse definieren können, wenn Sie zum Zeitpunkt der Klassendefinition eine Ausnahme auslösen. Dies ist eine Geschichte, die nichts mit "final" zu tun hat, daher habe ich bei der Definition der Klasse eine Ausnahme gemacht.

Zusammenfassung

Ich habe zwei Möglichkeiten eingeführt, um Überschreibungen zu unterdrücken. Ich schreibe es als Unterdrückung, weil es schließlich eine Lücke gibt, wenn Sie setattr verwenden. Ist es doch Python-ähnlich?

ähnliche Links

――Es scheint, dass es Menschen gibt, die genauso denken wie ich, und es gab einige Präzedenzfälle. (Ich beziehe mich nicht wirklich darauf, weil mir keine der Implementierungen gefallen hat oder ich sie überhaupt nicht verstanden habe.) - https://stackoverflow.com/questions/3948873/prevent-function-overriding-in-python - https://stackoverflow.com/questions/321024/making-functions-non-override-able - https://stackoverflow.com/questions/2425656/how-to-prevent-a-function-from-being-overridden-in-python

Ich mag dieses, das ist der dritte Link.

# We'll fire you if you override this method.

Recommended Posts

Das Unterdrücken von Methodenüberschreibungen in Python
Simplex-Methode (Einzelmethode) in Python
Implementierte Methode zur Weitergabe von Etiketten in Python
Simulieren Sie die Monte-Carlo-Methode in Python
Hash-Methode (Open-Address-Methode) in Python
Quadtree in Python --2
Python in der Optimierung
CURL in Python
Metaprogrammierung mit Python
Python 3.3 mit Anaconda
SendKeys in Python
Methode zum Erstellen einer Python-Umgebung in Xcode 6
Elektronenmikroskopsimulation in Python: Mehrschichtmethode (1)
Epoche in Python
Zwietracht in Python
Deutsch in Python
DCI in Python
Quicksort in Python
nCr in Python
N-Gramm in Python
Elektronenmikroskopsimulation in Python: Mehrschichtmethode (2)
Programmieren mit Python
Konstante in Python
FizzBuzz in Python
SQLite in Python
Schritt AIC in Python
LINE-Bot [0] in Python
CSV in Python
Reverse Assembler mit Python
Reflexion in Python
Konstante in Python
nCr in Python.
Scons in Python 3
Puyopuyo in Python
Python in Virtualenv
PPAP in Python
Quad-Tree in Python
Ausrichtungsalgorithmus durch Einfügemethode in Python
Reflexion in Python
Chemie mit Python
Johnson-Methode (Python)
Hashbar in Python
DirectLiNGAM in Python
LiNGAM in Python
In Python reduzieren
[Python] Semi-Lagrange-Methode
In Python flach drücken
Lernen Sie das Entwurfsmuster "Vorlagenmethode" in Python
Ich habe die Methode der kleinsten Quadrate in Python ausprobiert
Dynamisches Ersetzen der nächsten Methode in Python
Lernen Sie das Entwurfsmuster "Factory Method" in Python
Versuchen Sie, die Monte-Carlo-Methode in Python zu implementieren
Sortierte Liste in Python
Täglicher AtCoder # 36 mit Python
Clustertext in Python
AtCoder # 2 jeden Tag mit Python