classification
Title: Enum._convert shadows members named _convert
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.8, Python 3.7, Python 3.6
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: ethan.furman Nosy List: ethan.furman, orlnub123
Priority: normal Keywords: patch

Created on 2018-07-30 17:36 by orlnub123, last changed 2018-10-21 18:33 by orlnub123. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 8568 merged orlnub123, 2018-07-30 17:40
PR 9034 merged orlnub123, 2018-09-02 13:46
PR 9229 merged miss-islington, 2018-09-12 21:04
Messages (9)
msg322681 - (view) Author: (orlnub123) * Date: 2018-07-30 17:36
If an enum has a member named _convert it gets shadowed by the _convert method as shown below.

>>> import enum
>>>
>>> class Test(enum.Enum):
...     _convert = enum.auto()
...
>>> Test._convert
<bound method Enum._convert of <enum 'Test'>>

I've came up with a couple of solutions:

1. Add _convert to the invalid names list, next to mro
2. Rename _convert to _convert_ as sunder names are reserved
3. Move _convert to the metaclass

I think the first solution would be the worst as it would break existing enums that have _convert as a member (although unusable).

The problem with the second solution would be breaking external code that uses it although I think that's a moot point as it's meant for internal use. Another shortcoming would be having to update all the stdlib code that uses it.

The third solution might be a bit confusing on its own if an existing enum with a _convert member suddenly removed it leaving you with a bound method instead of raising an AttributeError.
msg324415 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2018-08-31 10:01
For versions 3.6 and 3.7 the solution is to modify the shadow check at line 236 so only DynamicClassAttributes are /not/ shadowed (meaning the _convert method would be shadowed by an _convert member).

For 3.8 we can move _convert to the metaclass: I wasn't originally supportive of this idea, but I can see it being useful for other Enum mix-ins such as str.  However, I will want to include a check that such a mix-in is there -- probably by checking that the Enum type is a subclass of the first member's type... something like:

    if not issubclass(cls, members[0]):
       raise TypeError(...)

While we're making that change we can also check that members is not empty and issue a friendlier error message.

We can also rename _convert to _convert_ in 3.8, but we'll need to have _convert also on the metaclass and have it trigger a warning that _convert_ is now the right way, and _convert will go away in 3.9.
msg324460 - (view) Author: (orlnub123) * Date: 2018-09-01 08:21
> For versions 3.6 and 3.7 the solution is to modify the shadow check at line 236 so only DynamicClassAttributes are /not/ shadowed (meaning the _convert method would be shadowed by an _convert member).

Doing that might not be backwards-compatible. An enum with a subclassed behavior and member with the same name would shadow the behavior in favor of the member with the change. I propose adding an extra if statement under it that sets the member if it's named _convert and the _convert method on the class belongs to Enum (so it's not a behavior).

> For 3.8 we can move _convert to the metaclass: I wasn't originally supportive of this idea, but I can see it being useful for other Enum mix-ins such as str.  However, I will want to include a check that such a mix-in is there -- probably by checking that the Enum type is a subclass of the first member's type... something like:
>
>     if not issubclass(cls, members[0]):
>        raise TypeError(...)

My thought for moving _convert to the metaclass was that it didn't make much sense for it to be accessible through a member. Could you elaborate on how mix-ins come into play?

> While we're making that change we can also check that members is not empty and issue a friendlier error message.

I don't quite follow, what would members be in this case?

> We can also rename _convert to _convert_ in 3.8, but we'll need to have _convert also on the metaclass and have it trigger a warning that _convert_ is now the right way, and _convert will go away in 3.9.

Sounds good.
msg324466 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2018-09-01 17:38
On 09/01/2018 01:21 AM, orlnub123 wrote:
> On 08/31/2018 03:01 ethan.furman wrote:

>> For versions 3.6 and 3.7 the solution is to modify the shadow check at
>> line 236 so only DynamicClassAttributes are /not/ shadowed (meaning
>> the _convert method would be shadowed by an _convert member).
> 
> Doing that might not be backwards-compatible. An enum with a subclassed
> behavior and member with the same name would shadow the behavior in
> favor of the member with the change. I propose adding an extra if
> statement under it that sets the member if it's named _convert and the
> _convert method on the class belongs to Enum (so it's not a behavior).

Fixing bugs is often not backwards-compatible.  ;)  I think the number
of affected people here will be low, though, because anybody who had an
_convert member should have noticed that they couldn't use it.
 
>> For 3.8 we can move _convert to the metaclass: I wasn't originally
>> supportive of this idea, but I can see it being useful for other Enum
>> mix-ins such as str.  However, I will want to include a check that
>> such a mix-in is there -- probably by checking that the Enum type is a
>> subclass of the first member's type... something like:
>>
>>      if not issubclass(cls, members[0]):
>>         raise TypeError(...)
> 
> My thought for moving _convert to the metaclass was that it didn't make
> much sense for it to be accessible through a member. Could you
> elaborate on how mix-ins come into play?

Blocking access through the member is a low priority.  For example,
originally Enum members were not available from each other (so
Color.Red.Blue didn't work), but that made accessing Color.Red _very_
slow.  The fix for the slow made it so Color.Red.Blue now "works",
although it is not recommended.

Also, moving _convert to the metaclass means it is now available on Enum,
Flag, and IntFlag, and every user-defined non-IntEnum class where it
wasn't before.  (This part will be in 3.8.)

The reason _convert exists at all is to be able to convert sets of global
constants into a drop-in replacement Enum, but that is only possible if
the drop-in Enum is compatible with the values being replaced -- so if
the values are integers (such as in the re module) then the replacement
Enum must be an IntEnum; if they were strings, then the replacement Enum
must be a StrEnum -- and the two cannot be mixed.

>> While we're making that change we can also check that members is not
>> empty and issue a friendlier error message.
> 
> I don't quite follow, what would members be in this case?

_convert is given a list of potential objects and a filter function to
select the ones to keep -- it is possible, most likely due to a bug in
user code, that the incoming list of objects is empty, or the filter
discards all of them, leaving members empty.
msg324471 - (view) Author: (orlnub123) * Date: 2018-09-01 23:07
> Fixing bugs is often not backwards-compatible.  ;)  I think the number
> of affected people here will be low, though, because anybody who had an
> _convert member should have noticed that they couldn't use it.

I think we had a misunderstanding, my proposal should be fully backwards-compatible. Maybe code can talk clearer than words? I'll attempt to turn it into a patch for 3.7 that can be backported to 3.6.

> Blocking access through the member is a low priority.  For example,
> originally Enum members were not available from each other (so
> Color.Red.Blue didn't work), but that made accessing Color.Red _very_
> slow.  The fix for the slow made it so Color.Red.Blue now "works",
> although it is not recommended.

That's honestly very interesting, thanks for the insight.

> Also, moving _convert to the metaclass means it is now available on Enum,
> Flag, and IntFlag, and every user-defined non-IntEnum class where it
> wasn't before.  (This part will be in 3.8.)

In my testings _convert has always been available to them as they all subclass Enum where it's defined. Was it originally meant to be defined on IntEnum?

> The reason _convert exists at all is to be able to convert sets of global
> constants into a drop-in replacement Enum, but that is only possible if
> the drop-in Enum is compatible with the values being replaced -- so if
> the values are integers (such as in the re module) then the replacement
> Enum must be an IntEnum; if they were strings, then the replacement Enum
> must be a StrEnum -- and the two cannot be mixed.

I understand and agree with the need for a check now however because of what I talked about above there should be a deprecation period similar to the one for renaming _convert. Would it be okay to add one?

> _convert is given a list of potential objects and a filter function to
> select the ones to keep -- it is possible, most likely due to a bug in
> user code, that the incoming list of objects is empty, or the filter
> discards all of them, leaving members empty.

My confusion came from the fact that it doesn't raise any errors currently if they're empty. Are you suggesting that it should raise one?
msg324472 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2018-09-01 23:47
orlnub123 wrote:
---------------
> In my testings _convert has always been available to them as they all
> subclass Enum where it's defined.

Wow, I really shouldn't check for things in the middle of the night.  On the bright side, no further changes are needed to _convert besides the rename.


orlnub123 wrote:
---------------
> I think we had a misunderstanding, my proposal should be fully
> backwards-compatible. Maybe code can talk clearer than words? I'll
> attempt to turn it into a patch for 3.7 that can be backported to 3.6.

Please do.  I'm curious to see what you come up with.
msg324933 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2018-09-10 16:39
New changeset c0d63bf73b35df374e6e66c08b0e297fb828d744 by Ethan Furman (orlnub123) in branch '3.7':
[3.7] bpo-34282: Fix Enum._convert method shadowing members named _convert (GH-9034)
https://github.com/python/cpython/commit/c0d63bf73b35df374e6e66c08b0e297fb828d744
msg325148 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2018-09-12 17:28
New changeset 0fb9fadd3b3e9e3698647e0b92d49b0b7aacd979 by Ethan Furman (orlnub123) in branch 'master':
bpo-34282: Fix Enum._convert shadowing members named _convert (GH-8568)
https://github.com/python/cpython/commit/0fb9fadd3b3e9e3698647e0b92d49b0b7aacd979
msg327214 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2018-10-06 03:10
New changeset 22e86fbbca04d251233fc07515885d2b67945094 by Ethan Furman (Miss Islington (bot)) in branch '3.6':
[3.7] bpo-34282: Fix Enum._convert method shadowing members named _convert (GH-9034) (GH-9229)
https://github.com/python/cpython/commit/22e86fbbca04d251233fc07515885d2b67945094
History
Date User Action Args
2018-10-21 18:33:39orlnub123setstatus: open -> closed
resolution: fixed
stage: patch review -> resolved
2018-10-06 03:10:09ethan.furmansetmessages: + msg327214
2018-09-12 21:04:24miss-islingtonsetpull_requests: + pull_request8660
2018-09-12 17:28:57ethan.furmansetmessages: + msg325148
2018-09-10 16:39:54ethan.furmansetmessages: + msg324933
2018-09-02 13:46:28orlnub123setpull_requests: + pull_request8496
2018-09-01 23:47:44ethan.furmansetmessages: + msg324472
2018-09-01 23:07:38orlnub123setmessages: + msg324471
2018-09-01 17:38:07ethan.furmansetmessages: + msg324466
2018-09-01 08:21:37orlnub123setmessages: + msg324460
2018-08-31 10:01:42ethan.furmansetmessages: + msg324415
2018-08-31 10:01:30ethan.furmansetmessages: - msg324414
2018-08-31 09:47:56ethan.furmansetmessages: + msg324414
2018-07-31 00:57:34rhettingersetassignee: ethan.furman
2018-07-30 17:40:33orlnub123setkeywords: + patch
stage: patch review
pull_requests: + pull_request8080
2018-07-30 17:36:39orlnub123create