Je l'ai affiché dans 3 au début, mais je l'ai déplacé parce que la partie d'hier de 1 n'était pas publiée et était vacante.
L'interdiction de la substitution de méthode pendant l'héritage peut être obtenue avec le mot-clé final
en Java et C ++, par exemple, mais ce n'est pas une fonctionnalité standard de Python.
Alors, j'ai recherché et réfléchi à la façon de le faire en Python3.
Toute cette implémentation se trouve sur https://github.com/eduidl/python-sandbox/tree/master/prohibit_override.
mypy
Les indicateurs de type ont été introduits à partir de Python 3.5, mais c'est un outil pour vérifier statiquement les types à l'aide des indicateurs de type.
C'est un tel mypy, mais vous pouvez utiliser le décorateur @ final
pour déclarer que c'est une méthode qui ne doit pas être remplacée.
L'utilisation est la suivante. Nous utilisons @ final
pour déclarer qu'il s'agit d'une méthode finale appelée bonjour
.
mypy.py
from typing_extensions import final
class Base:
@final
def hello(self) -> None:
print("hello")
class Derived(Base):
def hello(self) -> None:
print("Bonjour")
Base().hello()
Derived().hello()
Si vous utilisez mypy pour ce fichier, il vous en avertira.
$ 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)
Cependant, comme pour les indices de type, il n'y a aucun effet au moment de l'exécution, alors faites attention à cela.
$ python3 mypy.py
hello
Bonjour
Pros --Facile à présenter pour ceux qui utilisent déjà mypy -Il semble qu'il puisse également être utilisé pour les constantes
Cons
Depuis Python 3.8, il semble qu'il soit également implémenté dans le module typing
de la bibliothèque standard. Cela dit, vous avez toujours besoin d'un vérificateur de type, y compris mypy.
https://docs.python.org/ja/3.8/library/typing.html#typing.final
__init_subclass__
L'analyse statique par mypy est bonne, mais je suis heureux si je la vérifie au moment de l'exécution et lève une exception. En tant qu'image, cela ressemble à ʻabc` (https://docs.python.org/ja/3/library/abc.html), qui est un module de classe de base abstrait de la bibliothèque standard.
J'ai donc essayé de l'implémenter en référence à https://github.com/python/cpython/blob/3.8/Lib/abc.py (même si cela dit référence, il ne reste que __isfinalmethod__
. Je ressens).
C'est une implémentation simple qui correspond à toutes les méthodes avec la classe de base.
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))
Je l'ignore généralement, mais comme c'est un calendrier de l'avent, je vais l'expliquer brièvement.
final
def final(funcobj: AnyCallable) -> AnyCallable:
setattr(funcobj, '__isfinalmethod__', True)
return funcobj
Implémentation de ʻAbc.abstractmethod(https://github.com/python/cpython/blob/3.8/Lib/abc.py#L7-L25) Parce que PyCharm crache un avertissement. J'ai utilisé
setattr`.
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'
Je recherche juste une «méthode statique», une «méthode de classe» ou une fonction membre à utiliser dans le message d'erreur.
Final
Au début, j'ai écrit en utilisant une métaclasse, mais en me souvenant de "Python efficace" élément 33 dans Python 3.6 changements comme celui-ci, Réécrit en utilisant __init_subclass__
(certainement facile à utiliser).
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) prend un prédicat comme deuxième argument et renvoie ce qu'il est vrai .. Cette fois, je veux toutes les fonctions membres, la méthode statique et la méthode de classe, donc je vais rassembler celles pour lesquelles ʻinspect.isfunction
ou ʻinspect.ismethod` est vrai.
Final.__init_subclass__
Tout d'abord, concernant __init_subclass__
, il s'agit d'une fonction introduite dans Python 3.6, et pour plus de détails, reportez-vous au document officiel (https://docs.python.org/ja/3/reference/datamodel.html#object.init_subclass). Je vais citer.
Cette méthode est appelée chaque fois que la classe dans laquelle elle est définie est héritée. cls est une nouvelle sous-classe.
C'est tout, mais en bref, il est plus facile d'écrire que ce que j'avais l'habitude d'écrire en utilisant des métaclasses.
# Python 3.Jusqu'à 5
#En fait, j'utilise uniquement Exampale, pas Exampale Meta directement
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):
#divers
# Python 3.À partir de 6
class Exampale:
def __init__(self, args):
#divers
def __init_subclass__(cls, **kwargs):
some_func(cls)
Donc, si vous pouvez utiliser Python 3.6, je pense que vous devriez utiliser __init_subclass__
autant que possible. Donc, à propos de cette implémentation.
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}')
Obtenez toutes les méthodes avec Final.get_methods
, obtenez les classes héritées avec __bases__
, et pour chaque classe
__isfinalmethod__
et n'est-ce pas True
?
--Si «isfinalmethod» est «True», est-il remplacé?Vérifiez et le cas échéant, «Lever Type Error». Au fait, ʻabc lance également
TypeError`.
Préparez le cours suivant.
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
J'ai essayé quelques cas et cela semble fonctionner.
--Hériter A directement
class C(A):
def final_member(self) -> None:
pass
#=> Fail to declare class C, for override final member function: final_member
--Hériter de la classe (B) qui hérite de A
class D(B):
def final_member(self) -> None:
pass
#=> Fail to declare class D, for override final member function: final_member
--Héritage multiple
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
Un seul cas.
class J(A):
@classmethod
def final_class(cls) -> None:
pass
#=> Fail to declare class J, for override final class method: final_class
C'est également un seul cas.
class K(A):
@staticmethod
def final_static() -> None:
pass
#=> Fail to declare class K, for override final static method: final_static
Enfin, regardons le cas où aucune exception ne se produit. Ça me va.
class L(A):
def overridable(self) -> None:
print("from L")
L().overridable()
#=> from l
Pros
Cons
--Peut-être y a-t-il un surcoût d'exécution (bien que non mesuré) ―― Puisque l'implémentation est assez naïve, elle semble être un peu plus rapide si vous la gérez en utilisant set etc. (En fait, ʻabc` semble faire un cache en utilisant des références faibles https://github.com/python/cpython/blob/ 3.7 / Lib / _py_abc.py) ――Il semble impossible de faire une constante par cette méthode.
Dans le cas de ʻabc, une exception est levée au moment de l'instanciation, mais je pense que c'est parce que si vous soulevez une exception au moment de la définition de la classe, vous ne pourrez pas définir une classe abstraite. C'est une histoire qui n'a rien à voir avec
final`, j'ai donc fait une exception lors de la définition de la classe.
J'ai introduit deux façons de supprimer les remplacements.
Je l'écris comme suppression parce qu'il y a une faille après tout si vous utilisez setattr
. Est-ce comme Python après tout?
«Il semble qu'il y ait des gens qui pensent la même chose que moi, et il y a eu des précédents. (Je n'y fais pas vraiment référence parce que je n'aimais aucune des implémentations ou ne la comprenais pas du tout.) - 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
J'aime celui-ci, qui est le troisième lien.
# We'll fire you if you override this method.
Recommended Posts