[Python-Dev] PEP 435 - ref impl disc 2

Glenn Linderman v+python at g.nevcal.com
Tue May 14 07:01:17 CEST 2013

On 5/13/2013 7:36 PM, Ethan Furman wrote:
> On 05/10/2013 10:15 PM, Glenn Linderman wrote:
>> So it is quite possible to marry the two, as Ethan helped me figure 
>> out using an earlier NamedInt class:
>> class NIE( IntET, Enum ):
>>      x = ('NIE.x', 1)
>>      y = ('NIE.y', 2)
>>      z = ('NIE.z', 4)
>> and then expressions involving members of NIE (and even associated 
>> integers) will be tracked... see demo1.py.
>> But the last few lines of demo1 demonstrate that NIE doesn't like, 
>> somehow, remember that its values, deep down under
>> the covers, are really int.  And doesn't even like them when they are 
>> wrapped into IntET objects.  This may or may not
>> be a bug in the current Enum implementation.
> [demo1.py excerpt]
>     print( repr( NIE1( 1 ) + NIE1(2)))
>     print( repr( NIE1( IntET('NIE1.x', 1 )) + NIE1(2)))
>> So the questions are:
>> 1) Is there a bug in ref435 Enum that makes demo1 report errors 
>> instead of those lines working?
> Nope.

Well, if it isn't a bug, it will be interesting to read the 
documentation that explains the behavior, when the documentation is written:

The "obvious" documentation would be that Enum names values of any type, 
particularly the first type in the multiple-inheritance list. The values 
assigned to the enumeration members are used as parameters to the 
constructor of that first type, but the value of the enumeration member 
itself is an item of the type, created by the constructor.

The __call__ syntax  [ EnumDerivation( value ) ] looks up enumeration 
members by value.

The obvious documentation would stop there.  But if demo1 doesn't 
demonstrate a bug, it would have to continue, saying something like:

However, if you have a complex type, you can't look up by value, but 
rather have to resupply the constructor parameters used to create the 
item. This means that for simple types

EnumDerivation( EnumerationMember.value ) is EnumerationMember

but that doesn't hold for complex types.  I think it should.

>> 2) Is something like demo2 interesting to anyone but me? Of course, I 
>> think it would be great for reporting flag values
>> using names rather than a number representing combined bit fields.
> No idea.  ;)
>> 3) I don't see a way to subclass the ref435 EnumMeta except by 
>> replacing the whole __new__ method... does this mechanism
>> warrant a slight refactoring of EnumMeta to make this mechanism 
>> easier to subclass with less code redundancy?
> I've broken it down to make subclassing easier.

Thanks... I'll take a look, eventually, but I'll be offline until next week.

>> 4) Or is it simple enough and useful enough to somehow make it a 
>> feature of EnumMeta, enabled by a keyword parameter?
> Probably not.
>> 5) All this is based on "IntET"... which likely suffices for API 
>> flags parameters... but when I got to __truediv__ and
>> __rtruediv__, which don't return int, then I started wondering how to 
>> write a vanilla ET class that inherits from
>> "number" instead of "int" or "float"? One could, of course, make 
>> cooperating classes FloatET and DecimalET .... is this
>> a language limitation, or is there more documentation I haven't read? 
>> :)   (I did read footnote [1] of
>> <http://docs.python.org/3/reference/datamodel.html#emulating-numeric-types>, 
>> and trembled.)
> Sounds like a fun project (for some value of fun ;)

Not sure I'll get there, for a few years... such might be useful in 
certain debugging scenarios, but not sure it is useful enough to 
implement, given the footnote, except, perhaps, to truly become an 
expert in the Python object model.

> Okay, sorry for the long delay.
> What it comes down to is if you want to marry two complex types 
> together, you may have to be the counselor as well. ;)

:)  I assume by "counselor" you mean the code for __new__ and __init__ 
below, which, when I get a chance to understand them, will probably 
explain some of your earlier remarks about it maybe being easier to 
implement in such a manner.  Of course, I don't particularly want to 
marry the types, just have XxxEnum work for IntET as well as it does for 
int... I was bumping into name conflicts between Nick's implementation 
and yours, that weren't immediately obvious to me, because I haven't 
done multiple inheritance much — Enum is dragging me into that and 
metaclasses, though, which is a good thing for me, likely.

The one piece of "marriage" that is interesting is to avoid specifying 
the name twice, and it seems your code

> Here's your code, revamped.  I did make a slight change in the meta 
> --  I moved the name assignment above the __init__ call so it's 
> available in __init__.

That's handy, thanks.

> --8<--------------------------------------------------------
> from ref435 import Enum
> from flags import IntET
> class NIE1( IntET, Enum ):
>     x =  1
>     y =  2
>     z =  4
>     def __new__(cls, value):
>         member = IntET.__new__(cls, 'temp', value)
>         member._value = value
>         return member
>     def __init__(self, value):
>         self._etname = self._name
> print( repr( NIE1.x.value ))
> print( repr( NIE1.x + NIE1.y ))
> print( repr( NIE1.x + ~ NIE1.y))
> print( repr( NIE1.x + ~ 2 ))
> print( repr( NIE1.z * 3 ))
> print( repr( NIE1( 1 ) + NIE1(2)))
> print( repr( NIE1( IntET('NIE1.x', 1 )) + NIE1(2)))
> --8<--------------------------------------------------------
> and my results:
> 1
> IntET('(x + y)', 3)
> IntET('(x + ~y)', -2)
> IntET('(x + -3)', -2)
> IntET('(z * 3)', 12)
> IntET('(x + y)', 3)
> IntET('(x + y)', 3)

I'd expect  NIE1.x.value  to be  IntET('x', 1)  but I'll have to look 
more carefully at what you've done, when I have some time next week. You 
may have made some "simplifying assumptions", and things _should_ be as 
simple as possible, but no simpler... especially not if it leads to 
unexpected results.

> Oh, and if you really wanted the 'NEI' in the _etname, change the name 
> assignment:
>     self._etname = 'NIE.' + self._name

Sure.  I did, because one problem that might arise is the combination of 
NIE-style enums from different enumerations... not prohibited for 
IntEnum or NIE, because it gets converted to the base type (int or 
IntET, respectively).  But if someone accidentally combines an 
enumeration member from NIE1 and an enumeration member from NIE2, and it 
has the same member name, the expression could "look right" without the 
class name included.  So you see, including the class name was not just 
a whim, but the result of analyzing potential error cases.

> Forget to mention the good part -- in the custom __new__ you are able 
> to set the value to whatever you want (not a big deal in this case, 
> but if you had several parameters going in you could still make _value 
> be a single, simple int). 

This will take more thought than I have time for tonight, also. Right 
now, I think I want the value for NIE.x to be IntET('NIE.x', 1 ).  And 
your code isn't achieving that at present, but maybe I just need to 
tweak __new__ and then can... and maybe it cures the discrepancy in 
expectations mentioned earlier too...

On the other hand, when I think about it more, maybe I'll see what you 
are suggesting as a better path, for some reason.  But I think it is the 
case at present, that what you think I want, is different than what I 
think I want :)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20130513/34b79b81/attachment.html>

More information about the Python-Dev mailing list