__init__ being called on __str__ result in aenum.E

2019-07-24 08:05发布

问题:

While trying silly things to get my __str__ method working correctly on an Enum member, I discovered some behavior I don't really understand. I'm aware the following implementation is wrong, but I don't know why it does what it does.

Consider the following class:

from aenum import Enum, AutoValue
class MyEnum(str, Enum, settings=AutoValue, init="value data1 data2"):

    __str__ = lambda self: self

    def __new__(cls, name, *args, **kwargs):
        member = str.__new__(cls)
        member._value_ = name
        return member

    @staticmethod
    def _generate_next_value_(name, *args, **kwargs):
        return (name,) + args

    member1 = 1, 2
    member2 = 3, 4

On import, this works as exected. MyEnum.member1.data1 == 1 and MyEnum.member1.data2 == 2. However, when I call str(MyEnum.member1) an exception is thrown saying "missing value for 'data2'". I traced through the code here and it seems MyEnum.member1.__str__().__init__(MyEnum.member1.__str__()) is being called. I'm not positive that's the call path, but that's the result. And since MyEnum.__init__ is Enum.__init__ and we're using AutoValue, the init method is expecting two arguments. It's trying to set member1.data1 = member1 and member1.data2 doesn't have a value.

To be clear, I'm aware the correct solution is to implement the __str__ method as __str__ = str.__str__. However, since isinstance(MyEnum.member1, str) == True, and __str__ needs to return a str... I'm unclear why the above implementation would behave how it does.

  1. Why is any __init__ getting called?
  2. Why is it insufficient to return self since self is an str?
  3. Unrelated somewhat, by why could I not implement this without setting "value"? If you take away, value from the init string, remove the __new__ and _generate_next_value_ methods, on import a TypeError is thrown saying str() argument 2 must be str, not int (.../aenum/init.py:1302).

回答1:

Calling str on an object behaves like calling any other type: it calls str.__new__ on your object, and then if the result is an instance of str, it calls __init__ on the result.

str.__new__ calls your __str__ method, which returns a MyEnum. Since the return value is an instance of str, the next step is to call the __init__ method. That's where the unexpected __init__ call is coming from.



回答2:

@user2357112 has the right of it.

To elaborate on your point:

  1. Why is it insufficient to return self since self is an str?

self is not a str, it's a MyEnum which is a str subclass -- a subclass that has it's own __init__, which then gets called. The str class does not have an __init__ method.