I posted it in 3 at first, but I moved it because yesterday's part of 1 was unposted and was vacant.
Prohibition of method overriding at the time of inheritance can be realized by the final
keyword in Java or C ++, for example, but it is not a standard function of Python.
So, I researched and thought about how to do it in Python3.
All of this implementation is located at https://github.com/eduidl/python-sandbox/tree/master/prohibit_override.
mypy
Type hints were introduced from Python 3.5, but it is a tool for statically checking types using the type hints.
It's mypy, but you can use the @final
decorator to declare that it's a method that shouldn't be overridden.
The usage is as follows. We use @final
to declare that it is a final method called hello
.
mypy.py
from typing_extensions import final
class Base:
@final
def hello(self) -> None:
print("hello")
class Derived(Base):
def hello(self) -> None:
print("Hello")
Base().hello()
Derived().hello()
If you use mypy on this file, it will warn you.
$ 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)
However, as with Type Hints, there is no effect at runtime, so be careful about that.
$ python3 mypy.py
hello
Hello
Pros --Easy to introduce for those who are already using mypy ――It seems that it can also be used for constants
Cons --No error and no warning at runtime --Although the introduction itself can be entered with pip, the introduction cost is high for those who have not written type hints.
Since Python 3.8, it seems that it is also implemented in the typing
module of the standard library. That said, you still need some type checker, including mypy.
https://docs.python.org/ja/3.8/library/typing.html#typing.final
__init_subclass__
Static analysis by mypy is good, but I'm happy if I check it at runtime and raise an exception. As an image, it looks like ʻabc` (https://docs.python.org/ja/3/library/abc.html), which is an abstract base class module of the standard library.
So, I tried to implement it with reference to https://github.com/python/cpython/blob/3.8/Lib/abc.py (Although it is a reference, only __isfinalmethod__
remains. I feel like that).
It's a simple implementation that matches all the methods with the base class.
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))
I usually skip it, but since it is an Advent calendar, I will explain it briefly.
final
def final(funcobj: AnyCallable) -> AnyCallable:
setattr(funcobj, '__isfinalmethod__', True)
return funcobj
ʻAbc.abstractmethodimplementation (https://github.com/python/cpython/blob/3.8/Lib/abc.py#L7-L25) Because PyCharm spits out a warning. I used
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'
I'm just looking for a static method
, a class method
, or a member function to use for error messages.
Final
I wrote it using a metaclass at first, but remembering "Effective Python" item 33 in Python 3.6 changes like this, Rewritten using __init_subclass__
(certainly easy to use).
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) takes a predicate as the second argument and returns what it is true .. This time, I want all the member functions, static method, and class method, so I will collect the ones for which ʻinspect.isfunction
or ʻinspect.ismethod` is true.
Final.__init_subclass__
First of all, regarding __init_subclass__
, it is a function introduced in Python 3.6, and for details, refer to the official document (https://docs.python.org/ja/3/reference/datamodel.html#object.init_subclass). I will quote.
This method is called whenever the class in which it is defined is inherited. cls is a new subclass.
That's all, but in short, what I used to write using metaclasses is easier to write.
# Python 3.Up to 5
#Actually, I only use Exampale, not Exampale Meta directly
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):
#various
# Python 3.From 6
class Exampale:
def __init__(self, args):
#various
def __init_subclass__(cls, **kwargs):
some_func(cls)
So, if you can use Python 3.6, I think you should use __init_subclass__
as much as possible. So, about this implementation.
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}')
Get all methods with Final.get_methods
, get inherited classes with __bases__
, and for each class
--Do you have a method with the same name?
--If you have it, it has the __isfinalmethod__
attribute and it is not True
--If __isfinalmethod__
is True
, is it overridden?
Check and if applicable, raise Type Error
. By the way, ʻabc also throws
TypeError`.
Prepare the following class.
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
I've tried a few cases and it seems to work.
--Inherit A directly
class C(A):
def final_member(self) -> None:
pass
#=> Fail to declare class C, for override final member function: final_member
--Inherit class (B) that inherits A
class D(B):
def final_member(self) -> None:
pass
#=> Fail to declare class D, for override final member function: final_member
--Multiple inheritance
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
--Override with class method
class G(A):
@classmethod
def final_member(cls) -> None:
pass
#=> Fail to declare class G, for override final member function: final_member
--Override with static method
class H(A):
@staticmethod
def final_member() -> None:
pass
#=> Fail to declare class H, for override final member function: final_member
Only one case.
class J(A):
@classmethod
def final_class(cls) -> None:
pass
#=> Fail to declare class J, for override final class method: final_class
This is also only one case.
class K(A):
@staticmethod
def final_static() -> None:
pass
#=> Fail to declare class K, for override final static method: final_static
Finally, let's look at the case where no exception occurs. Sounds okay.
class L(A):
def overridable(self) -> None:
print("from L")
L().overridable()
#=> from l
Pros
--Can detect overrides and raise exceptions at runtime
Cons
--Maybe there is run-time overhead (although not measured) --Since the implementation is quite naive, it seems to be a little faster if you manage it using set etc. (Actually ʻabc` seems to make a cache using weak references https://github.com/python/cpython/blob/ 3.7 / Lib / _py_abc.py) ――It seems impossible to make a constant by this method.
In the case of ʻabc, an exception is raised at the time of instantiation, but I think that this is because if you raise an exception at the time of class definition, you will not be able to define an abstract class. This is a story that has nothing to do with
final`, so I made an exception when defining the class.
I have introduced two ways to suppress overrides.
I write it as suppression because there is a loophole after all if you use setattr
. Does that mean it's Python-like after all?
――There seemed to be some people who think the same as myself, and there were some precedents. (I don't really refer to it because I didn't like any of the implementations or didn't understand it at all.) - 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
I like this one, which is the third link.
# We'll fire you if you override this method.
Recommended Posts