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/

Reply via email to