mkorpela / overrides Goto Github PK
View Code? Open in Web Editor NEWA decorator to automatically detect mismatch when overriding a method
License: Apache License 2.0
A decorator to automatically detect mismatch when overriding a method
License: Apache License 2.0
In my project, I have a class hierarchy that resembles the following.
from __future__ import annotations
import overrides
class Parent(overrides.EnforceOverrides):
def child(self) -> Child:
raise NotImplementedError
class Child(Parent):
@overrides.overrides
def child(self) -> Child:
return self
Since overrides are validated on class creation and not instantiation (which is a good thing and should remain the default), the type Child
is not yet defined when the type annotations of Child.children
are evaluated so an error is raised. It would be cool if there was a way to opt-into deferred validated (e.g., @overrides.overrides(defer=True)
to defer the validation at the cost of a small performance hit.
When I use mypy --strict, I get:
error: Class cannot subclass 'EnforceOverrides' (has type 'Any')
This error occurs because mypy --strict contains the --disallow-subclassing-any flag, which prevents subclasses that return Any from being subclassed with more specific type annotations, e.g.
class Player(EnforceOverrides):
def __init__(self) -> None:
pass
I've tried looking into this issue but I've gotten stuck. Maybe it has to do with adding type hints...
apparently I don't have enough SO reputation to just leave a comment there..
feature request:
have you thought about a class-level decorator that checks for @Final decorators in parent classes, for all methods, not just those marked with @OVERRIDES? i'm thinking it will be easier to remember a single class-level decorator than it would be to try and mark every method with @OVERRIDES and work backwards to resolve issues..
class Foo(Super1,
Super2)
@overrides
def bar(self):
pass
==> CRASH
python 3.4 open shell, using version0.5,
the following line: import overrides generates the following error:
ImportError: cannot import name 'VERSION'
In the latest overrides
version (6.1.0
), the following code snippet leads to errors:
class A:
def generic_func(self, *args, **kwargs):
raise NotImplementedError
class B(A):
@overrides
def generic_func(self, actual_arg, some_kwarg=1):
.
.
.
It can be pain to require setting @overrides(check_signature=False)
if several classes in the code inherit from a class like A
. Is there a way to ignore the check for *args
and **kwargs
?
Hi,
Starting v5.0.0 or greater, when you try to override a method which originally isn't supported for type hints, the overriding breaks. Caught in AllenNLP and Grammarly/Gector
Steps to reproduce:
from overrides import overrides
from typing import Any
class BaseClass:
def do_something(self, arg1, arg2=None):
pass
class ChildClass(BaseClass):
@overrides
def do_something(self, arg1:str, arg2:Any=None) -> Any:
pass
throws the TypeError
:
Traceback (most recent call last):
File "main.py", line 9, in <module>
class ChildClass(BaseClass):
File "main.py", line 12, in ChildClass
def do_something(self, arg1:str, arg2:Any=None) -> Any:
File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/overrides.py", line 88, in overrides
return _overrides(method, check_signature, check_at_runtime)
File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/overrides.py", line 114, in _overrides
_validate_method(method, super_class, check_signature)
File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/overrides.py", line 135, in _validate_method
ensure_signature_is_compatible(super_method, method, is_static)
File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/signature.py", line 86, in ensure_signature_is_compatible
ensure_all_positional_args_defined_in_sub(
File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/signature.py", line 212, in ensure_all_positional_args_defined_in_sub
raise TypeError(
TypeError: `ChildClass.do_something: arg1 must be a supertype of `<class 'inspect._empty'>` but is `<class 'str'>`
When overriding the following interface:
class MyInterface(ABC):
@abstractmethod
def run(self, callback: Callable[[str], None]):
pass
overrides will fail with:
lib/python3.7/site-packages/overrides/overrides.py:88: in overrides
return _overrides(method, check_signature, check_at_runtime)
lib/python3.7/site-packages/overrides/overrides.py:114: in _overrides
_validate_method(method, super_class, check_signature)
lib/python3.7/site-packages/overrides/overrides.py:135: in _validate_method
ensure_signature_is_compatible(super_method, method, is_static)
lib/python3.7/site-packages/overrides/signature.py:84: in ensure_signature_is_compatible
super_sig, sub_sig, super_type_hints, sub_type_hints, is_static, method_name
lib/python3.7/site-packages/overrides/signature.py:138: in ensure_all_kwargs_defined_in_sub
and not _issubtype(super_type_hints[name], sub_type_hints[name])
python3.7/site-packages/overrides/signature.py:41: in _issubtype
return issubtype(left, right)
python3.7/site-packages/typing_utils/__init__.py:274: in issubtype
return _is_normal_subtype(normalize(left), normalize(right), forward_refs)
python3.7/site-packages/typing_utils/__init__.py:177: in normalize
args = tuple(normalize(a) for a in args)
python3.7/site-packages/typing_utils/__init__.py:177: in <genexpr>
args = tuple(normalize(a) for a in args)
python3.7/site-packages/typing_utils/__init__.py:171: in normalize
return NormalizedType(_ensure_builtin(tp))
python3.7/site-packages/typing_utils/__init__.py:49: in _ensure_builtin
if tp in BUILTINS_MAPPING:
E TypeError: unhashable type: 'list'
I believe this is an issue in the underlying typing-utils library that it doesn't properly handle the first parameter of Callable being a list (which is unhashable).
This library has a @Final decorator as well, would be nice to document how does it square with:
https://www.python.org/dev/peps/pep-0591/#the-final-decorator
When from __future__ import annotations
is imported, all annotations are converted into strings. Currently, the code incorrectly assumes that type annotations are objects. A simple fix to this problem is to use the typing.get_type_hints
function in the standard library to extract type hints instead of using inspect.signature
. This will become the default behavior in Python 3.10, so we will have to do this eventually.
Add signature checking to overrides decorator (not just Enforced case).
Also add an argument to decorator to disable it in cases where the check is invalid.
@overrides(ignore_signature=True)
def foobar(x=4, y:str="3", *argscheckignored):
pass
Hi, I encountered an import error after upgrading overrides to 4.1.1.
Here is what I got
Python 3.6.12 |Anaconda, Inc.| (default, Sep 8 2020, 17:50:39)
[GCC Clang 10.0.0 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import overrides
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/miniconda3/envs/PycharmProjects/lib/python3.6/site-packages/overrides/__init__.py", line 1, in <module>
from overrides.enforce import EnforceOverrides
File "/opt/miniconda3/envs/PycharmProjects/lib/python3.6/site-packages/overrides/enforce.py", line 169, in <module>
class EnforceOverrides(metaclass=EnforceOverridesMeta):
File "/opt/miniconda3/envs/PycharmProjects/lib/python3.6/site-packages/overrides/enforce.py", line 133, in __new__
setattr(getattr(mcls, method), "__ignored__", True)
AttributeError: 'int' object has no attribute '__ignored__'
not sure this is worth a true "fix", a note in the docs / example may be enough:
the order / interaction of (some) decorators seems to matter, e.g. (in python 2.7):
from overrides import overrides
class A:
@property
def foo( self ):
pass
class B( A ):
@overrides
@property
def foo( self ):
pass
leads to "AttributeError: 'property' object has no attribute 'globals'", but
class B( A ):
@property
@overrides
def foo( self ):
pass
works exactly as expected; btw, declaring class A( object ):
vs. class A:
didn't make a difference
If a method is marked as @final
, an error should be risen if subclass tries to override it.
Currently it only works if the subclass's method is decorated with @overrides
, if this decorator is omitted, no error is shown. (I have not tested it, but it seems to be so from looking at the code.)
I suggest to create a class StrictParent
, from which other classes using the decorators could inherit from. StrictParent
would have a metaclass, so it could detect the violations of @foo
even if the subclass's method does not use @overrides
.
This solution could be used to solve #12 as well.
Since one might need to use the @final
and @overrides
features in an ABC class, StrictParent
's metaclass should inherit from ABCMeta
in order to prevent metaclass conflicts.
The following fails:
from typing import Generic, TypeVar
from overrides import overrides
TObject = TypeVar("TObject", bound="Foo")
class Foo(Generic[TObject]):
"""Foo class."""
def foo_method(self):
"""Foo method."""
pass
class Bar(Foo["Bar"]):
"""Bar class."""
@overrides
def foo_method(self):
return 5
converter = Bar()
print(converter.foo_method())
with
Traceback (most recent call last):
File "bla.py", line 15, in <module>
class Bar(Foo["Bar"]):
File "bla.py", line 17, in Bar
@overrides
File "/Users/patrik/.local/share/virtualenvs/dbbsim-j0PjGvEt/lib/python3.7/site-packages/overrides/overrides.py", line 70, in overrides
method.__name__)
AssertionError: No super class method found for "foo_method"
from overrides import EnforceOverrides, overrides
class SuperClass(EnforceOverrides):
someClassVariable = 'foo'
class SubClass(SuperClass):
someClassVariable = 'bar'
Produces:
Traceback (most recent call last):
File "test.py", line 8, in <module>
class SubClass(SuperClass):
File "/Users/wtregask/Documents/c2s_backend_6/build/c2s-backend/venv/lib/python3.7/site-packages/overrides/enforce.py", line 19, in __new__
'Method %s overrides but does not have @overrides decorator' % (name)
AssertionError: Method someClassVariable overrides but does not have @overrides decorator
Unfortunately, there's nothing the programmer can do about that - class variables cannot be decorated.
Overrides should ignore any attributes of the class that aren't callable.
Here is the error from [email protected]:
@overrides File "/Users/abc/virtualenv/xyz/lib/python3.6/site-packages/overrides/overrides.py", line 58, in overrides for super_class in _get_base_classes(sys._getframe(2), method.__globals__): File "/Users/abc/virtualenv/xyz/lib/python3.6/site-packages/overrides/overrides.py", line 75, in _get_base_classes class_name_components in _get_base_class_names(frame)] File "/Users/abc/virtualenv/xyz/lib/python3.6/site-packages/overrides/overrides.py", line 96, in _get_base_class_names if type(co.co_consts[oparg]) == str: IndexError: tuple index out of range
I tried to fix it locally but had problem to run the tests. Note I just upgrade from [email protected] to @3.6.4 and use virtualenv for isolation. Let me know if there is anything I could help.
Thanks,
Leo
Currently, subclasses are required to have a return type-hint if the superclass defines one.
import overrides
class SuperClass(overrides.EnforceOverrides):
def foo(self) -> int:
return 1
class SubClass(SuperClass):
@overrides.overrides
def foo(self):
return 2
# TypeError: SubClass.foo: return type `None` is not a `<class 'int'>`.
This is because and sub_return is not None
is missing on the following line.
overrides/overrides/signature.py
Lines 286 to 288 in deb0f3f
Classifiers are correctly stated, but these are only for browsing Pypi and are not used during installation:
Line 29 in 8cfef00
Python3 should be declared as a python_requires:
https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
Otherwise, Python2 installs 3.x.x, but receives a syntax error during import overrides:
overrides/overrides/overrides.py
Line 28 in 8cfef00
Hi!
I noticed that the LICENSE file isn't included in the pypi distribution --- it'd be great if this was added for the next release.
Thanks!
self
is a special param. Because of multiple inheritance self might not be able to actually obey normal requirements of subtype param to be contravariant wikipedia Inheritance.
I believe that self
should be assumed and allowed to be bound to current class. It is also safer not to complain.
( see #60 (comment) )
import abc
from typing import TypeVar
from overrides import EnforceOverrides, final, overrides
BaseType = TypeVar("BaseType", bound="A")
SubType = TypeVar("SubType", bound="B")
class A(abc.ABC, EnforceOverrides):
@abc.abstractmethod
def copy(self: BaseType) -> BaseType:
"""Copy an object."""
class B(A):
@final
@overrides
def copy(self: SubType) -> SubType: # TypeError: `~SubType` is not a `~BaseType`.
return self.helper() # For this to be valid, `self` must be typed as a subclass of `B`
@abc.abstractmethod
def helper(self: SubType) -> SubType:
"""Helper for copy(), specific to subclasses of `B`."""
class C(B):
def helper(self) -> "C":
return C()
C().copy()
Because register
is defined in abc.ABCMeta
and handle_special_value
is defined in EnforceOverridesMeta
, the code incorrectly determines that classes that redefine these methods are missing an @overrides
decorator. Instead, EnforceOverridesMeta
should ignore methods that are missing an @overrides
decorator if they are defined on EnforceOverridesMeta
.
class RegisterRepro(overrides.EnforceOverrides):
def register(self):
pass
# AssertionError: Method register overrides but does not have @overrides decorator
class HandleSpecialValueRepro(overrides.EnforceOverrides):
def handle_special_value(self):
pass
# AssertionError: Method handle_special_value overrides but does not have @overrides decorator
0.5.0
to PyPI? I have a fairly large class hierarchy in my project that would exercise a lot of the new functionality (e.g., everything is type-hinted, there are self-types, there are static methods, etc.). Unfortunately, changing the build infrastructure to use git references instead of pip packages is a little hard in this project. Getting 0.5.0
to work on my project should give us additional confidence going into the 0.5.0
release.I'm happy to see signature enforcement feature, cool stuff, but it broke how I was doing inheritance in my library. Maybe I'm wrong to do this, but it's valid python and it seems to be a missed edge case.
Here's a toy example that exposes the issue. In this design pattern the base class has a concrete set of optional parameters that I want to be able to add to in the future without updating every subclass to add the same optional arguments and defaults. The base class is the source of truth for this behavior.
Subclasses can add their own unique parameters to the signature, and pass the remainder on to the superclass without requiring knowledge of the signature or duplication of the default parameters.
from overrides import overrides, EnforceOverrides
class A(EnforceOverrides):
def methoda(self, x=0):
print(x)
class B(A):
# this fails validation because x is missing
@overrides
def methoda(self, y=1, **kwargs):
print(y)
super().methoda(**kwargs)
However, this results in this stack trace:
File "C:\ProgramData\Anaconda\lib\site-packages\overrides\enforce.py", line 155, in __new__
ensure_signature_is_compatible(base_class_method, value)
File "C:\ProgramData\Anaconda\lib\site-packages\overrides\enforce.py", line 34, in ensure_signature_is_compatible
ensure_all_args_defined_in_sub(super_sig, sub_sig)
File "C:\ProgramData\Anaconda\lib\site-packages\overrides\enforce.py", line 47, in ensure_all_args_defined_in_sub
raise TypeError(f"`{name}` is not present.")
TypeError: `x` is not present.
As a side note, it took me a bit to track down the issue because the TypeError didn't specify which method was missing the parameter "x". That would be a nice enhancement to the message to include the function name.
I found I could get around the issue by changing the base class to use **kwargs, but I'd prefer not to have to do that for readability.
from overrides import overrides, EnforceOverrides
class A(EnforceOverrides):
def methoda(self, **kwargs):
x = kwargs.get("x", 0)
print(x)
class B(A):
@overrides
def methoda(self, y=1, **kwargs):
print(y)
super().methoda(**kwargs)
For the following class:
from concurrent.futures import Future
class MyInterface(ABC):
@abstractmethod
def run(self) -> "Future[str]":
pass
Overrides will fail with a hard error due to:
File "lib/python3.6/site-packages/overrides/overrides.py", line 67, in overrides
return _overrides(method, check_signature, check_at_runtime)
File "lib/python3.6/site-packages/overrides/overrides.py", line 93, in _overrides
_validate_method(method, super_class, check_signature)
File "lib/python3.6/site-packages/overrides/overrides.py", line 114, in _validate_method
ensure_signature_is_compatible(super_method, method, is_static)
File "lib/python3.6/site-packages/overrides/signature.py", line 75, in ensure_signature_is_compatible
super_type_hints = _get_type_hints(super_callable)
File "lib/python3.6/site-packages/overrides/signature.py", line 46, in _get_type_hints
return get_type_hints(callable)
File "lib/python3.6/typing.py", line 1543, in get_type_hints
value = _eval_type(value, globalns, localns)
File "lib/python3.6/typing.py", line 350, in _eval_type
return t._eval_type(globalns, localns)
File "lib/python3.6/typing.py", line 245, in _eval_type
eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
TypeError: 'type' object is not subscriptable
Hard failure may not be the correct answer here: IMO the unevaluatable type hint should be ignored.
I tried marking an __enter__
method as final, but this doesn't work:
class MultiResourceManager(ExitStack, EnforceOverrides):
@overrides
@final
def __enter__(self):
with ExitStack() as stack:
stack.push(self)
self.acquire_resources()
stack.pop_all()
return self
@abstractmethod
def acquire_resources(self) -> None:
pass
class OverridesEnter(MultiResourceManager):
def __enter__(self):
# this should fail!
pass
The EnforceOverridesMeta
class explicitly excludes names that start with double underscores (see enforce.py
lines 10-11). I suspect the intention of this is to exclude names like __module__
or __qualname__
. However, those could be more appropriately filtered by checking callable(value)
instead.
The updated code would be:
class EnforceOverridesMeta(ABCMeta):
def __new__(mcls, name, bases, namespace, **kwargs):
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
for name, value in namespace.items():
# Actually checking the direct parent should be enough,
# otherwise the error would have emerged during the parent class checking
if not callable(value): # <--- this line is different
continue
value = mcls.handle_special_value(value)
is_override = getattr(value, '__override__', False)
for base in bases:
base_class_method = getattr(base, name, False)
if not base_class_method or not callable(base_class_method):
continue
assert is_override, \
'Method %s overrides but does not have @overrides decorator' % (name)
# `__finalized__` is added by `@final` decorator
assert not getattr(base_class_method, '__finalized__', False), \
'Method %s is finalized in %s, it cannot be overridden' % (base_class_method, base)
return cls
@staticmethod
def handle_special_value(value):
if isinstance(value, classmethod):
value = value.__get__(None, dict)
elif isinstance(value, property):
value = value.fget
return value
Relevant scenarios:
__enter__
to ensure a behavior / safety by marking it final.__init__
is part of the interface (e.g. for factory functions operating on types), marking it as final.@overrides
markers on magic functions to make it explicit when such functions are being overriden.Note that the change as proposed above is not backwards compatible. To maintain backwards compatibility the existing class could be maintained and an additional class could be added with the new behavior (e.g. EnforceOverridesAll
).
We use your decorator for a project, but require the change made in #16, which is making it a little tricky for us to release our project onto pypi (or at least, easily). A new release would be much appreciated!
If you run this in a REPL it fails (Python 3.8). I've stared at this for an hour and I have no idea why it fails. Do you guys have any ideas?
import overrides
class Foo:
class Bar(overrides.EnforceOverrides):
def to_dict(self):
pass
class Car(Bar):
@overrides.overrides
def to_dict(self):
pass
To be clear, this works fine.
class Foo:
class Bar:
def to_dict(self):
pass
class Car(Bar):
def to_dict(self):
pass
It'd be great if there were a way for overridden methods to amend the parent docstring in some way - even if just through some kind of dumb string concatenation. Sometimes the overriding method has more useful information to add - e.g. subclass-specific details or behaviours - but having to copy-paste the entire parent docstring creates a maintenance burden, since now the docstrings must be carefully, manually kept synchronised.
Even better would be if common docstring formats were understood, and the merge done more intelligently than just a string concatenation - e.g. param / return values in the override get individually appended to the corresponding lines from the parent.
The exact behavour desired - re. append vs merge vs replace - could be controlled via an optional argument to @overrides
.
At least shouldn't crash the code - ignore the check or fix the problem.
This is because inspect.stack() will find the row from the source file.
How about add it to travis or any other continuous integration, and test code coverage?
I encountered a strange error that I managed to reproduce with the following example:
from typing import Callable, Type
from overrides import overrides
class SuperClass(object):
def method(self) -> int:
"""This is the doc for a method and will be shown in subclass method too!"""
return 2
def my_decorator(name: str) -> Callable:
def func(cls: Type) -> Type:
return cls
return func
class MyClass:
def __init__(self, name: str):
self.my_name: str = name
def my_func() -> None:
my_object = MyClass("Slartibartfast")
@my_decorator(my_object.my_name)
class SubClass(SuperClass):
@overrides
def method(self) -> int:
return 1
my_func()
The resulting error message:
Traceback (most recent call last):
File "repro.py", line 33, in <module>
my_func()
File "repro.py", line 27, in my_func
class SubClass(SuperClass):
File "repro.py", line 29, in SubClass
def method(self) -> int:
File "/home/username/.local/lib/python3.6/site-packages/overrides/overrides.py", line 59, in overrides
for super_class in _get_base_classes(sys._getframe(2), method.__globals__):
File "/home/username/.local/lib/python3.6/site-packages/overrides/overrides.py", line 76, in _get_base_classes
class_name_components in _get_base_class_names(frame)]
File "/home/username/.local/lib/python3.6/site-packages/overrides/overrides.py", line 76, in <listcomp>
class_name_components in _get_base_class_names(frame)]
File "/home/username/.local/lib/python3.6/site-packages/overrides/overrides.py", line 173, in _get_base_class
obj = getattr(obj, component)
AttributeError: 'function' object has no attribute 'my_name'
Hi,
I just ran this program:
from overrides import overrides
class SuperClass(object):
def method(self): return 2
class SubClass(SuperClass):
@overrides
def method(self): return 1
And got an error message.
Traceback (most recent call last):
File "C:\Users\admin\Test.py", line 9, in <module>
class SubClass(SuperClass):
File "C:\Users\admin\Test.py", line 11, in SubClass
@overrides
File "C:\Users\admin\AppData\Local\Programs\Python\Python36\lib\site-packages\overrides\overrides.py", line 58, in overrides
for super_class in _get_base_classes(sys._getframe(2), method.__globals__):
File "C:\Users\admin\AppData\Local\Programs\Python\Python36\lib\site-packages\overrides\overrides.py", line 75, in _get_base_classes
class_name_components in _get_base_class_names(frame)]
File "C:\Users\admin\AppData\Local\Programs\Python\Python36\lib\site-packages\overrides\overrides.py", line 96, in _get_base_class_names
if type(co.co_consts[oparg]) == str:
IndexError: tuple index out of range
It seems to me, as if this was caused by updating from Python 3.5 to 3.6.
Best,
r0f1
@staticmethod
@overrides
def get_all_statements(player_index: int) -> List[Statement]:
AssertionError: Method get_all_statements overrides but does not have @OVERRIDES decorator
@overrides
@staticmethod
def get_all_statements(player_index: int) -> List[Statement]:
@staticmethod
File "/Users/tyleryep/miniconda3/lib/python3.7/site-packages/overrides/overrides.py", line 57, in overrides
for super_class in _get_base_classes(sys._getframe(2), method.globals):
AttributeError: 'staticmethod' object has no attribute 'globals'
When overriding a classmethod, the override decorator isn't recognised.
@classmethod
@abstractmethod
def method(cls):
pass
@classmethod
@overrides
def method(cls):
return 1
AssertionError: Method method overrides but does not have @OVERRIDES decorator
Python 3.4.0, pip 7.0.1
Command pip install overrides
fails with
Collecting overrides
Using cached overrides-0.4.tar.gz
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 20, in <module>
File "/tmp/pip-build-evd5k9b1/overrides/setup.py", line 5, in <module>
from overrides import __VERSION__
File "/tmp/pip-build-evd5k9b1/overrides/overrides/__init__.py", line 2, in <module>
from overrides import __VERSION__
ImportError: cannot import name '__VERSION__'
----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-evd5k9b1/overrides
The final
and overrides
decorators do not contain type annotations. As a result, adding these decorators to functions breaks mypy
type checking (the annotations are 'forgotten').
Adding the following type stubs would fix this:
from typing import Any, Callable, TypeVar
_FuncT = TypeVar('_FuncT', bound=Callable[..., Any])
def final(method: _FuncT) -> _FuncT:
...
def overrides(method: _FuncT) -> _FuncT:
...
Note that there is a separate issue of a module and decorator sharing a name (overrides
). Some care must be taken to avoid mypy
getting confused by this. By putting the above stubs in __init__.pyi
in an overrides-stubs
package (installed with pip), this potential confusion was avoided.
I'll add this new issue as a discussion opener. Thanks for great work @ashwin153 !
**kwargs and *args are a bit special and seems that current implementation does not handle them yet correctly.
Basically both need to be also present in sub method if present in the super method. BUT
one can always detach elements from there.
I assume the logic would be the following (please correct me if wrong):
Still have to triple check this from https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Inheritance_in_object-oriented_languages
I would also want this to eventually go to the plane @overrides
decorator but that might be also done later.
Hello,
A lot of the code I'm currently writing relies pretty heavily on TypeVar
definitions. Woke up this morning and found out that some of it stopped running!
It seems like the newest release of overrides
adds some stricter signature checking, which throws an error when my parent class's function signatures rely on TypeVars:
import abc
from typing import TypeVar
from overrides import EnforceOverrides, overrides
T = TypeVar("T", bound="ParentClass")
class ParentClass(abc.ABC, EnforceOverrides):
@abc.abstractmethod
def copy(self: T) -> T: # Specify that the output should have the same type as `self`
"""Return a copy of myself."""
class Subclass(ParentClass):
@overrides
def copy(self) -> "Subclass": # TypeError: `Subclass` is not a `~T`.
return Subclass()
I get a TypeError: 'Subclass' is not a '~T'.
message, because typing_utils.issubtype
doesn't consider Subclass
here to be a subtype of TypeVar("T", bound="ParentClass")
.
Is this expected behavior? The setup is a little niche, but in my experience it's at least well-supported by mypy
.
Thanks!!
Hello,
I don't think that this is a problem with your code, but perhaps you could help me to solve this.
PyCharm offers a method to insect the code of a project.
If I am using your override annotation (thanks a lot), I get the following warning:
Problem synopsis 'overrides' is not callable (at line ...)
One warning for each usage of the decorator.
I am also using "@abc.abstractmethod" decorators but there is no problem detected.
So, could you help me, why "abs" decorations are working without "problems" and your "overrides" raise such a warning?
I could ignore this IDE problem, as it is working, but it would be nice to get such stuff suppressed.
Hi, I updated to version 2.1 and start getting AssertonError:
def count_vocab_items(self, token: Token, counter: Dict[str, Dict[str, int]]):
File "/home/generall/sources/Sci/ml/word_gan/venv/lib/python3.6/site-packages/overrides/overrides.py", line 58, in overrides
for super_class in _get_base_classes(sys._getframe(2), method.__globals__):
File "/home/generall/sources/Sci/ml/word_gan/venv/lib/python3.6/site-packages/overrides/overrides.py", line 75, in _get_base_classes
class_name_components in _get_base_class_names(frame)]
File "/home/generall/sources/Sci/ml/word_gan/venv/lib/python3.6/site-packages/overrides/overrides.py", line 75, in <listcomp>
class_name_components in _get_base_class_names(frame)]
File "/home/generall/sources/Sci/ml/word_gan/venv/lib/python3.6/site-packages/overrides/overrides.py", line 167, in _get_base_class
obj = getattr(namespace["__builtins__"], components[0])
AttributeError: 'dict' object has no attribute 'int'
It'd be fantastic if the checks performed by @OverRide could be extended to include validation of the parameters, to ensure they are the same (with certain exceptions such as the use of *args & **kwargs) and have the same type information (where present).
There are commits indicating Python 2 support, but it's not in the Trove classifiers nor the tox.ini, so it will suffer bit-rot easily.
This is obviously mainly for Python 2.7, but Python 2.6 would probably not be a significant extra burden and apparently still sees significant use.
So, the decorator you've created is pretty cool. However, it doesn't enforce usage.
What I'd like to see, as a slight variation on your example, kinda like the reverse of what @abstractmethod
in abstract superclass will do if not implemented in subclass
from overrides import overrides
class SuperClass(EnforceOverrides):
def method(self):
"""This is the doc for method and will be shown in subclass method too!"""
return 2
class SubClass(SuperClass):
# error, because the @overrides annotation is missing
def method(self):
return 1
I'm not 100% sure this is possible in Python (might really require a linting plugin or metaclass?), but it would be really useful for enforcing good code documentation.
Is the check done every method call or only at class creation? What is the performance impact? It would be nice to know this README.
Add GH Actions to validate
mypy workings together with overrides and python 3.6 compatibility.
Toy example that triggers the problem (tested in Python 3.7):
from typing import Generic, TypeVar
from overrides import overrides
class Foo:
pass
T1 = TypeVar('T1')
class SuperClass(Generic[T1]):
pass
# this class causes error, but ONLY w/ the @overrides annotation
class SubClass1(SuperClass[float]):
@overrides # gives wrong error message, even if defined in SuperClass
def foo(self):
pass
class SubClass2(SuperClass[Foo]):
@overrides # behaves as expected
def foo(self):
pass
This triggers the following:
Traceback (most recent call last):
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 165, in _get_base_class
obj = namespace[components[0]]
KeyError: 'float'During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:/Users/Dylan/.PyCharmCE2019.2/config/scratches/scratch_1.py", line 14, in
class SubClass1(SuperClass[float]):
File "C:/Users/Dylan/.PyCharmCE2019.2/config/scratches/scratch_1.py", line 15, in SubClass1
@OVERRIDES # gives wrong error message, even if defined in SuperClass
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 58, in overrides
for super_class in _get_base_classes(sys._getframe(2), method.globals):
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 75, in _get_base_classes
class_name_components in _get_base_class_names(frame)]
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 75, in
class_name_components in _get_base_class_names(frame)]
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 167, in _get_base_class
obj = namespace["builtins"][components[0]]
TypeError: 'module' object is not subscriptableProcess finished with exit code 1
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.