-
-
Notifications
You must be signed in to change notification settings - Fork 31k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-40066: Enum: modify repr()
and str()
#22392
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. How did you track down all the docs that need to be changed? (I note there aren't any doc changes do to the change in str().)
Enum's `repr()` and `str()` have changed: `repr()` is now *EnumClass.MemberName* | ||
and `str()` is *MemberName*. Additionally, stdlib Enum's whose contents are | ||
available as module attributes, such as `RegexFlag.IGNORECASE`, have their | ||
`repr()` as *module.name*, e.g. `re.IGNORECASE`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe also add a section to what's new in 3.10?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought blurb
did that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, blurb just updates Misc/NEWS, which is an endless list of things (though more focused than the commit log :-).
What's new in 3.10 is a separate doc written by human editor who curate the most important news and group it by affected area.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the versionchanged
directive is needed in the module documentation.
Assuming no one comes up with a strong reason to not commit this PR, I will need to do some rewriting of |
That doesn't look for occurrences of the str() though, which doesn't have any funky markup.
Are you planning to do that in the same PR? (I recommend it.) FWIW you're unlikely to get anyone to tell you not to commit this from GitHub reviewers or bpo readers. Was there a python-ideas thread? Maybe you want to ask Raymond (if he didn't already concur)? |
Doc/library/enum.rst
Outdated
>>> ~Perm.RWX | ||
<Perm.-8: -8> | ||
Perm.-8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case I would prefer ~Perm.RWX
or Perm(-8)
. But this may be a different issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Negative values are no longer a thing -- they are converted to their positive equivalent.
Doc/library/enum.rst
Outdated
|
||
Another important difference between :class:`IntFlag` and :class:`Enum` is that | ||
if no flags are set (the value is 0), its boolean evaluation is :data:`False`:: | ||
|
||
>>> Perm.R & Perm.X | ||
<Perm.0: 0> | ||
Perm.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perm(0)
would look better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with 0x0
to match other non-Flag values, similarly to the way re.RegexFlag
does it.
Doc/library/enum.rst
Outdated
>>> bool(Perm.R & Perm.X) | ||
False | ||
|
||
Because :class:`IntFlag` members are also subclasses of :class:`int` they can | ||
be combined with them:: | ||
|
||
>>> Perm.X | 8 | ||
<Perm.8|X: 9> | ||
Perm.8|Perm.X |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here I would prefer Perm.X|8
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is now Perm.X|0x8
Doc/library/enum.rst
Outdated
@@ -1198,7 +1198,7 @@ constructor. For example:: | |||
... example = '11', 16 # '11' will be interpreted as a hexadecimal | |||
... # number | |||
>>> MyEnum.example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that it would more useful to use MyEnum.example.value
or int(MyEnum.example)
in the example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I added the .value
here and in other places.
Lib/enum.py
Outdated
raise ValueError(f'_sunder_ names, such as "{key}", are ' | ||
'reserved for future Enum use') | ||
raise ValueError( | ||
'_sunder_ names, such as %r, are reserved for future Enum use' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use f-string with !r
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I'm not using f-strings anywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point I'll switch all the %-interpolations to use f-strings.
Lib/enum.py
Outdated
for m in self.__class__: | ||
if value & m._value_: | ||
value &= ~m._value_ | ||
members.append('re.%s' % (m._name_, )) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops. Looks like I need to add a test for that branch of code.
Lib/enum.py
Outdated
@@ -946,3 +942,42 @@ def _decompose(flag, value): | |||
# we have the breakdown, don't need the value member itself | |||
members.pop(0) | |||
return members, not_covered | |||
|
|||
def global_int_repr(self): | |||
return '%s.%s' % (self.__class__.__module__, self.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if add an attribute to the enum class which is set to the class name by default and can be set to the module name for enums whose members are exposed as globals?
return '%s.%s' % (self.__class__._namespacename, self.name)
It will make easier customization of the repr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think writing a custom __repr__
is not that difficult.
Lib/enum.py
Outdated
value &= ~m._value_ | ||
members.append('re.%s' % (m._name_, )) | ||
if value: | ||
members.append(hex(value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For general code I would add a class method for converting value to string. Depending on the class octal or binary may be better than hexadecimal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point.
Enum's `repr()` and `str()` have changed: `repr()` is now *EnumClass.MemberName* | ||
and `str()` is *MemberName*. Additionally, stdlib Enum's whose contents are | ||
available as module attributes, such as `RegexFlag.IGNORECASE`, have their | ||
`repr()` as *module.name*, e.g. `re.IGNORECASE`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the versionchanged
directive is needed in the module documentation.
Lib/enum.py
Outdated
self._name_ = '%s' % '|'.join([ | ||
str(m._name_ or m._value_) for m in members | ||
]) | ||
return self._name_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now whichever of __str__
and __repr__
is called first will initialize _name_
for all time. Even if they do the same thing now, they might drift in the future. Should this initialization code be in a common function to make sure it's consistent? One option would be to override name() and do it there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name is now initialized when the pseudo-member is created.
Who is this PR waiting for now? |
GvR wrote:
For me to make a few changes, and hopefully for Raymond to chime in. I'll update the bpo issue in case he didn't see my email. |
non-StrEnum `__str__` will print the member name StrEnum `__str__` will print the member value
if `enum.__str__` is either `Enum.__str__` or `global_enum_str` use the value's `__format__`
repr() = class.member_name str() = member_name
rename to `global_enum` and have `global_enum` update the module's namespace with the members remove `_stdlib_enum` where the enums were not being exported to the module's namespace
* rename global_int_repr --> global_enum_repr * adjust global_flag_repr to print hex number for out-of-range values * extract function when _generate_next_value_ is a static method
- repr() is now ``enum_class.member_name`` - str() is now ``member_name`` - add HOW-TO section for ``Enum`` - change main documentation to be an API reference
ea6308a
to
7643863
Compare
@gvanrossum RE: looking for |
repr()
and str()
repr()
and str()
In Python 3.10 the repr and str representation of Enums changed from str: 'ClassName.NAME' -> 'NAME' repr: '<ClassName.NAME: value>' -> 'ClassName.NAME' which is more consistent with what str/repr should do, however this breaks boilerplate which needs to get the ClassName.NAME version in all versions of Python. Thus, we locally monkey patch our preferred str representation in here. bpo-40066 python/cpython#22392
In Python 3.10 the repr and str representation of Enums changed from str: 'ClassName.NAME' -> 'NAME' repr: '<ClassName.NAME: value>' -> 'ClassName.NAME' which is more consistent with what str/repr should do, however this breaks boilerplate which needs to get the ClassName.NAME version in all versions of Python. Thus, we locally monkey patch our preferred str representation in here. bpo-40066 python/cpython#22392
Enumerations created from
Enum._convert_
will have theirrepr()
changed to bemodule.member_name
and their
str()
to bemember_name
The exception is
StrEnum
, whosestr()
will bevalue
as in, the member's value.
https://bugs.python.org/issue40066