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.
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
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
__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))
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
__isfinalmethod__
und ist es nicht True
?
--Wenn __isfinalmethod__`` True
ist, wird es überschrieben?Überprüfen Sie und erhöhen Sie gegebenenfalls den Typfehler. Übrigens wirft abc
auch TypeError
.
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
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
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
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
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
abc
einen Cache mit schwachen Referenzen zu erstellen. Https://github.com/python/cpython/blob/ 3.7 / Lib / _py_abc.py)
―― Mit dieser Methode scheint es unmöglich, eine Konstante zu erstellen.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.
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?
――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