[issue43010] @functools.wraps and abc.abstractmethod interoperability

2021-03-22 Thread Erez Zinman


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

2021-03-22 Thread Erez Zinman


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

2021-03-22 Thread Erez Zinman


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__`

2021-03-22 Thread Erez Zinman


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

2021-03-22 Thread Erez Zinman


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

2021-03-22 Thread Erez Zinman


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

2021-05-06 Thread Erez Zinman


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

2021-05-06 Thread Erez Zinman


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

2021-05-06 Thread Erez Zinman


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

2021-01-23 Thread Erez Zinman


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

2020-09-09 Thread Erez Zinman


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

2020-09-09 Thread Erez Zinman


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

2020-09-09 Thread Erez Zinman


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

2020-09-09 Thread Erez Zinman


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

2020-09-09 Thread Erez Zinman


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

2020-09-09 Thread Erez Zinman


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