Something to keep in mind as these discussions continue: Enums are already
unusual in several ways:
- the class itself is iterable
- the class itself supports containment checks of its enum members
- the enum members are created, and guaranteed singletons, during class creation
- the enum members are instannces of the class
- calling the class with a value, which in other classes/types would return a
new instance, instead
returns one of the existing enum members, or raises if such a member does not
exist
- and probably other things I'm forgetting at the moment
Flag is a subclass of Enum. Some of its peculiarities:
- bitwise operations are supported, and return a "new" instance of the combined
value
- the "new" instance is also a singleton (so `(F1 | F2) is (F1 | F2) is True`
- the "new" instance is an instance of the Flag class
- flags are iterable -- `list(F1) == [F1]; list(F1 | F2) == [F1, F2]`
- iterating over a Flag only returns the "pure" (aka single-bit) flags, even if
multi-flag instances
have been created (at least, they will in 3.11)
On 11/8/21 8:32 PM, Guido van Rossum wrote:
> On Mon, Nov 8, 2021 at 7:26 PM Ethan Furman wrote:
>>
>> Let's use a concrete example: `re.RegexFlag`
>>
>> ```
>> Help on function match in module re:
>>
>> match(pattern, string, flags=0)
>> Try to apply the pattern at the start of the string, returning
>> a Match object, or None if no match was found.
>> ```
>>
>> In use we have:
>>
>> result = re.match('present', 'who has a presence here?',
re.IGNORECASE|re.DOTALL)
>>
>> Inside `re.match` we have `flags`, but we don't know if `flags` is nothing
(0), a single flag (re.ASCII, maybe) or a
>> group of flags (such as in the example). For me, the obvious way to check
is with:
>>
>> if re.IGNORECASE in flags: # could be re.I in 0, re.I in 2, re.I in
5, etc.
>> ...
>
> Yes.
Note that the 0, 2, and 5 above are representative of the flag value passed in.
>> Now, suppose for the sake of argument that there was a combination of flags
that had its own code path, say
>>
>> weird_case = re.ASCII | re.LOCALE | re.MULTILINE
>>
>> I can see that going two ways:
>>
>> weird_case in flags # if other flags allowed
>>
>
> I would spell this as
>
> if flags & weird_case == weird_case:
> (which BTW works too if `flags` and `weird_case` are sets!)
That is a legitimate way to spell that operation, although sets wouldn't actually work since re.match expects an integer
or a RegexFlag.
>> or
>>
>> weird_case is flags # if no other flags allowed
>>
>
> this should be spelled using `==`, not using `is` (which is reserved for
identity, but here we're comparing values).
Instances of combined flags are also guaranteed singletons, so `is` is fine
(unless we're talking style choices).
>> The idiom that I'm shooting for is using `in` to check for flags:
>>
>> - flag1 in flag1 True
>> - flag1 in (flag1 | flag2) True
>> - flag3 in (flag1 | flag2) True
>
> Did you mean False for this last one?
Nope. flag3 is flag1 | flag2, so the same as (flag1 | flag2) in (flag1 |
flag2). I should probably have made that clearer.
>> - flag3 in flag1 False
>> - flag3 in flag4 False
>
> When `flag` is a single flag bit and `flags` is an unknown combination of
flag bits, you can conceivably
> make it so you can write this as `flag in flags`. But `in` doesn't work when
sets are represented as bits
> in an integer (which is the analogy you're going for?)
Technically, they work just fine. The bits and integers are an implementation detail, but one I suspect many, if not
most, of us are used to.
>> and
>>
>> - flag0 in any flag False
>>
>> - any flag in flag0 False
>
> What is `any flag` here?
Any combinaation of flags, including no flags at all.
>> And, of course, if we want to know if the thing we have is exactly flag1:
>>
>> flag is flag1
>>
>> will tell us.
>
>
> Please use `==` here.
Flags are guaranteed singletons, just like Enums.
>> Does this make sense?
>
> When thinking about sets of flag bits, do you actually visualize them as bits
in an integer?
Yes, but that's an implementation detail (albeit an efficient one).
> That's how I always think of them. Suppose we have three flags, F1, F2 and
F3. Let's write them in binary:
>
> F1 = 0b001
Agreed with all of that. My "weird case" above was in relation to the code used to handle that particular combination
of flags, not that the flags variable had more than one flag specified.
I've had a couple people tell me that they think of flags as sets, and use set theory / set behavior to understand how
flags and groups of flags should interact. My main concern is whether that is the only correct way to look at flags,
and whether the behavior that seems logical to me with regards to, for example, RegexFlag(0), is wrong, unusual, or
perfectly natural.
The way I see it, the following should hold
empty_flag = RegexFlag(0)
any_case = RegexFlag.IGNORECASE
any_case_on_any_line = RegexFlag.IGNORECASE | RegexFlag.MULTILINE
any_case in empty_flag is False
any_case_on_any_line in empty_flag is False
empty_flag in any_case is False
empty_flag in any_case_on_any_line is False
--
~Ethan~
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/python-dev@python.org/message/LPR4WNGX677LH6DFVEDUQUD3K73IY3G6/
Code of Conduct: http://python.org/psf/codeofconduct/