[issue43010] @functools.wraps and abc.abstractmethod interoperability
Erez Zinman added the comment: Sorry for the late response. I forgot about that. I believe one of us misunderstands the problem. The problem is that `functools.wraps` copies the `__isabstractmethod__` in the first place. Consider the following example: ``` class ModuleTemplate(ABC): @abstractmethod def _internal_main_operation(self, a: int, b: str, c: list) -> bool: pass @functools.wraps(_internal_main_operation) def external_main_operation(self, *args, **kwargs): print('LOG: Operation started.') try: ret = self._internal_main_operation(*args, **kwargs) except: print('LOG: Operation finished successfully.') raise else: print('LOG: Operation finished successfully.') return ret class ModulePositiveCheck(ModuleTemplate): def _internal_main_operation(self, a: int, b: str, c: list) -> bool: return a > 0 ``` In that case, I have a class that has a main operation that can be called either from the outside or internally. The outside call may be wrapped with some aspect-specific functionality like thread-safety, logging, exception handling, etc.; and the internal call implements the core logic of the class. In that (pretty common) pattern, I wouldn't want the `wraps` function to copy all the `abc` attributes because they're irrelevant. In fact, I'm having trouble thinking of a case where you WOULD like these attributes to be copied. The solution here, I think, is to exclude these attributes from being copied within `functools.update_wrapper`. If you do want to allow copying these attributes (I don't see why, but anyway), you could add an `excluded` parameter to `update_wrapper`. -- ___ Python tracker <https://bugs.python.org/issue43010> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue43010] @functools.wraps and abc.abstractmethod interoperability
Erez Zinman added the comment: I haven't because I don't need it anymore and it will surely work. Yet by doing so, other attributes will not be copied. I think that the `abc`-related attributes are irrelevant regardless, and should be omitted (at least by default). -- ___ Python tracker <https://bugs.python.org/issue43010> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue43010] @functools.wraps and abc.abstractmethod interoperability
Erez Zinman added the comment: This is an interoperability bug. Maybe not a severe one (due to the workaround), but it's still a bug. -- ___ Python tracker <https://bugs.python.org/issue43010> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue43594] A metaclass that inherits both `ABC` and `ABCMeta` breaks on `__subclasscheck__`
New submission from Erez Zinman : Consider the following example: ``` from abc import ABCMeta, ABC class MetaclassMixin(ABC): pass class Meta(MetaclassMixin, ABCMeta): pass class A(metaclass=Meta): pass ``` Then the call `isinstance(A, Meta)` returns `True` but `isinstance(1, Meta)` raises >>> TypeError: __subclasscheck__() missing 1 required positional argument: >>> 'subclass' Checked on 3.6.9, 3.8.0 & 3.8.8 -- components: Library (Lib) messages: 389314 nosy: erezinman priority: normal severity: normal status: open title: A metaclass that inherits both `ABC` and `ABCMeta` breaks on `__subclasscheck__` versions: Python 3.6, Python 3.8 ___ Python tracker <https://bugs.python.org/issue43594> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue43595] Can not add a metclass that inherits both ABCMeta & ABC to a Union
New submission from Erez Zinman : Related to Issue #43594. When running the following code ``` from abc import ABCMeta, ABC from typing import Union class MetaclassMixin(ABC): pass class Meta(MetaclassMixin, ABCMeta): pass print(Union[str, Meta]) ``` An exception is raised >>> TypeError: descriptor '__subclasses__' of 'type' object needs an argument Tested on v3.6.9 -- messages: 389317 nosy: erezinman priority: normal severity: normal status: open title: Can not add a metclass that inherits both ABCMeta & ABC to a Union versions: Python 3.6 ___ Python tracker <https://bugs.python.org/issue43595> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue43595] Can not add a metclass that inherits both ABCMeta & ABC to a Union
Erez Zinman added the comment: This is actually a lot worse and unrelated to the aforementioned issue. The code ``` from typing import Union from abc import ABC Union[str, ABC] ``` raises the exception >>> TypeError: descriptor '__subclasses__' of 'type' object needs an argument -- ___ Python tracker <https://bugs.python.org/issue43595> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue44057] Inconsitencies in `__init_subclass__` in a generic class
New submission from Erez Zinman : The following behavior was witnessed in v3.6 & v3.8. When deriving from a Generic base class, there's an inconsistency in the order of operation within the `__new__()` function between the case of deriving WITH generic-argument specification and WITHOUT. It might be best explained in the following example: ``` import typing T = typing.TypeVar('T') class Base(typing.Generic[T]): some_attribute: typing.Any def __init_subclass__(cls, **kwargs): assert hasattr(cls, 'some_attribute') class Class1(Base): # OK some_attribute = 123 class Class2(Base[int]): # AssertionError some_attribute = 123 ``` In this examples, the base class implements `__init_subclass__` to ensure that sublclasses define an attribute. In the case of `Class1`, the class derives without specifying the type-arguments for the class. In that case, the `__init_subclass__` is called after the `some_attribute` is defined. In the second case, however, because I pass the `int` type-argument to the base-class, for some reason `__init_subclass__` is called BEFORE the class' definition. -- components: Interpreter Core, Library (Lib) messages: 393085 nosy: erezinman priority: normal severity: normal status: open title: Inconsitencies in `__init_subclass__` in a generic class versions: Python 3.6, Python 3.8 ___ Python tracker <https://bugs.python.org/issue44057> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue44057] Inconsitencies in `__init_subclass__` in a generic class
Erez Zinman added the comment: You're right. I accidentally used 3.6.9 both times. Thank you anyway. Regardless, that's unfortunate that you don't support the version 3.8 anymore, since many frameworks do not officially support 3.9 as of yet (pytorch, for example). -- ___ Python tracker <https://bugs.python.org/issue44057> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue44057] Inconsitencies in `__init_subclass__` in a generic class
Erez Zinman added the comment: Also Tensorflow. -- ___ Python tracker <https://bugs.python.org/issue44057> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue43010] @functools.wraps and abc.abstractmethod interoperability
New submission from Erez Zinman : Consider the following code: ``` from abc import ABC, abstractmethod from functools import wraps class A(ABC): @abstractmethod def f(self): pass @wraps(f) def wrapper(self): print('f is called!') f() class B(A): def f(self): print('f!') B() ``` The last line of code results in the following error: >>> TypeError: Can't instantiate abstract class B with abstract methods wrapper That happens because `wraps` copies the `__dict__` of the original function. The problem is that at the point of declaration, the `__dict__` also contained `__isabstractmethod__=True` so it was copied as well, and it caused an error on the class' instantiation even though it contained no abstract methods. Moreover, this behavior is misleading because the the wrapper function is not abstract. Thanks. -- components: Extension Modules, Library (Lib) messages: 385538 nosy: erezinman priority: normal severity: normal status: open title: @functools.wraps and abc.abstractmethod interoperability type: behavior versions: Python 3.6 ___ Python tracker <https://bugs.python.org/issue43010> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41751] Error copying an instance of a subclass of OrderedDict
New submission from Erez Zinman : This bug occurs when copying/pickling an ordered-dict subtype that has default items. The initialization function that's returned is **not** `object.__new__` so the default items are set when the copied/pickled item is created. The problem I encountered is that when deleting an initial item, it appears in the copy. See the MWE below: ``` from collections import OrderedDict import copy class A(OrderedDict): def __init__(self): self['123'] = 123 a = A() del a['123'] copy.copy(a) # --> A([('123', 123)]) ``` This can cause other problems as well, because you don't assume that the class is re-initialized on deserialization/copy. -- components: Library (Lib) messages: 376627 nosy: erezinman priority: normal severity: normal status: open title: Error copying an instance of a subclass of OrderedDict type: behavior versions: Python 3.6 ___ Python tracker <https://bugs.python.org/issue41751> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41751] Error copying an instance of a subclass of OrderedDict
Erez Zinman added the comment: Proposed solution - change OrderedDict's `__reduce_ex__ ` function. The code below works like expected if I override `__reduce_ex__` like so: ``` def __reduce_ex__(self, protocol): ret = list(super().__reduce_ex__(protocol)) ret[0] = type(self).__new__ ret[1] = type(self), return tuple(ret, ) ``` -- ___ Python tracker <https://bugs.python.org/issue41751> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41751] Error copying an instance of a subclass of OrderedDict
Erez Zinman added the comment: Looking at the implementation of `__init__` (https://github.com/python/cpython/blob/76553e5d2eae3e8a47406a6de4f354fe33ff370b/Lib/collections/__init__.py#L109), it seems that you can fix this bug while retaining backward compatibility if you would use `__new__` in the `__reduce__` function, followed by an `OrderedDict.__init__`. The difference is that you don't call `__class__.__init__`, but rather `OrderedDict.__init__`. Some thing like the following: def __reduce__(self): 'Return state information for pickling' inst_dict = vars(self).copy() for k in vars(OrderedDict()): inst_dict.pop(k, None) def initializer(): inst = __class__.__new__(__class__) OrderedDict.__init__(inst) return initializer, (), inst_dict or None, None, iter(self.items()) The items will "restored" later using the `odict_iter`. -- ___ Python tracker <https://bugs.python.org/issue41751> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41751] Error copying an instance of a subclass of OrderedDict
Erez Zinman added the comment: small correction: meant `self.__class__` in the above function. Also, it is based on https://github.com/python/cpython/blob/76553e5d2eae3e8a47406a6de4f354fe33ff370b/Lib/collections/__init__.py#L302 -- ___ Python tracker <https://bugs.python.org/issue41751> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41751] Error copying an instance of a subclass of OrderedDict
Erez Zinman added the comment: The first argument can be remedied by creating a new method that deals with that (something like `def _new_init(cls)`, and passing the `cls` as argument). The third argument is not really an argument - there is a bug that needs a precedent to be solved. Concerning the second argument, I think it is only problematic when pickling in a new version of python and unpickling in an old one, and not vice versa (because the reduction from previous versions calls __init__). In the old->new direction, there wouldn't be much of a problem because the worst thing that happens, is that this bug could occur. If that doesn't satisfy you, I hope that this change can be made in the future, when a version that allows such a lack of backward-compatibility is released. -- ___ Python tracker <https://bugs.python.org/issue41751> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41751] Error copying an instance of a subclass of OrderedDict
Erez Zinman added the comment: Just to be clear, what I did is (works with both "copy" and "pickle"): ``` def _new_and_init(cls): inst = cls.__new__(cls) OrderedDict.__init__(inst) return inst class A(OrderedDict): def __reduce__(self): # This fixes a bug in Python 3.6 ret = list(super().__reduce__()) ret[0] = _new_and_init ret[1] = (self.__class__, ) return tuple(ret) ``` And that works... I also verified that indeed old->new pickling will just call _init__ on unpickling, so this would just retain the previous behavior. -- ___ Python tracker <https://bugs.python.org/issue41751> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com