Suppression des substitutions de méthode en Python

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.

environnement

la mise en oeuvre

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

référence

Postscript

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

Utilisation de __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))

Détails d'implémentation

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

Vérifiez et le cas échéant, «Lever Type Error». Au fait, ʻabc lance également TypeError`.

Exemple d'utilisation

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

Remplacement de la fonction membre

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

remplacement de méthode de classe

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

remplacement de méthode statique

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

Ordinaire

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.

Supplément

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.

Résumé

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?

Liens connexes

«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

Suppression des substitutions de méthode en Python
Méthode Simplex (méthode unique) en Python
Implémentation de la méthode de propagation d'étiquettes en Python
Simuler la méthode Monte Carlo en Python
Méthode Hash (méthode d'adresse ouverte) en Python
Quadtree en Python --2
Python en optimisation
CURL en Python
Métaprogrammation avec Python
Python 3.3 avec Anaconda
SendKeys en Python
Méthode pour créer un environnement Python dans Xcode 6
Simulation au microscope électronique en Python: méthode multi-coupes (1)
Époque en Python
Discord en Python
Allemand en Python
DCI en Python
tri rapide en python
nCr en python
N-Gram en Python
Simulation au microscope électronique en Python: méthode multi-coupes (2)
Programmation avec Python
Constante en Python
FizzBuzz en Python
Sqlite en Python
Étape AIC en Python
LINE-Bot [0] en Python
CSV en Python
Assemblage inversé avec Python
Réflexion en Python
Constante en Python
nCr en Python.
Scons en Python 3
Puyopuyo en python
python dans virtualenv
PPAP en Python
Quad-tree en Python
Algorithme d'alignement par méthode d'insertion en Python
Réflexion en Python
Chimie avec Python
Méthode Johnson (python)
Hashable en Python
DirectLiNGAM en Python
LiNGAM en Python
Aplatir en Python
[Python] Méthode Semi-Lagrange
Aplatir en python
Apprenez le modèle de conception "Méthode de modèle" en Python
J'ai essayé la méthode des moindres carrés en Python
Pour remplacer dynamiquement la méthode suivante en python
Apprenez le modèle de conception "Méthode d'usine" en Python
Essayez d'implémenter la méthode Monte Carlo en Python
Liste triée en Python
AtCoder # 36 quotidien avec Python
Texte de cluster en Python
AtCoder # 2 tous les jours avec Python