Use definition order of Enum as natural order - python

I'm trying to create an Enum subclass whose values use their definition order as their natural sort order, like in the example below:
#functools.total_ordering
class SelectionType(enum.Enum):
character = 'character'
word = 'word'
sentence = 'sentence'
paragraph = 'paragraph'
def __le__(self, other):
if not isinstance(other, SelectionType):
return NotImplemented
return self._positions[self] < self._positions[other]
SelectionType._positions = {x: i for i, x in enumerate(SelectionType)}
Is there a more direct way to get the position of an enum value in its definition order or otherwise a better way to do this?

If this is a pattern you need often, or if the values are important and cannot be replaced by numbers, make a custom Enum you can inherit from:
import enum
class ByDefinitionOrderEnum(enum.Enum):
def __init__(self, *args):
try:
# attempt to initialize other parents in the hierarchy
super().__init__(*args)
except TypeError:
# ignore -- there are no other parents
pass
ordered = len(self.__class__.__members__) + 1
self._order = ordered
def __ge__(self, other):
if self.__class__ is other.__class__:
return self._order >= other._order
return NotImplemented
def __gt__(self, other):
if self.__class__ is other.__class__:
return self._order > other._order
return NotImplemented
def __le__(self, other):
if self.__class__ is other.__class__:
return self._order <= other._order
return NotImplemented
def __lt__(self, other):
if self.__class__ is other.__class__:
return self._order < other._order
return NotImplemented
This allows you to keep any other value instead, while still sorting according to definition order.
class SelectionType(ByDefinitionOrderEnum):
character = 'character'
word = 'word'
sentence = 'sentence'
paragraph = 'paragraph'
and in use:
>>> SelectionType.word < SelectionType.sentence
True
>>> SelectionType.word.value < SelectionType.sentence.value
False

You could encode the positions as values. Use .name to get the name.
class SelectionType(enum.Enum):
character = 1
word = 2
sentence = 3
paragraph = 4
# copy the OrderedEnum recipe from https://docs.python.org/3/library/enum.html#orderedenum
def __lt__(self, other):
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
>>> SelectionType.word.name
'word'
>>> SelectionType.word < SelectionType.sentence
True
On Python 3.6+ you could use enum.auto() to avoid hard-coding the positions.
class SelectionType(enum.Enum):
character = enum.auto()
word = enum.auto()
sentence = enum.auto()
paragraph = enum.auto()

Related

Print a Python variable in hexadecimal by default

I wish to display some variables, data members in hexadecimal format any time they are printed.
My current approach is to define a class Hex:
class Hex:
"""Facilitate printing an integer in hexadecimal format."""
def __init__(self, data):
if not isinstance(data, int):
raise ValueError("Hex expects an integer.")
self._data = data
def __repr__(self):
return hex(self._data)
def __eq__(self, other):
return self._data == other
def __lt__(self, other):
return self._data < other
def __le__(self, other):
return self._data <= other
def __gt__(self, other):
return self._data > other
def __ge__(self, other):
return self._data >= other
def __add__(self, right):
return self._data + right
def __radd__(self, left):
return self._data + left
def __hash__(self):
return hash(self._data)
This works all right as the following examples demonstrate it:
address = Hex(0xdeadbeef)
record1 = {'address': address, 'counter': 42}
print(record1)
{'address': 0xdeadbeef, 'counter': 42}
The __hash__ is defined so that the object is hashable and can be used as a key in a dict:
record2 = {address: 'address'}
print(record2)
{0xdeadbeef: 'address'}
The equality and comparisons are defined so that handling of duplicates and sorting works:
list1 = [Hex(0xff), 15, 23, Hex(0xf), 255]
print(list1)
[0xff, 15, 23, 0xf, 255]
print(set(list1))
{15, 23, 0xff}
list1.sort()
print(list1)
[15, 0xf, 23, 0xff, 255]
The __add__ and __radd__ are defined so that pointer arithmetic is supported:
new_address = Hex(record1['address'] + 0x20400000)
print(new_address)
0xfeedbeef
address += 0x3f00
address += Hex(0xfe)
print(address)
0xdeadfeed
And now to the questions.
Is there a built in or existing hex integer that somehow has its own __repr__ attached that prints it in hex, but otherwise it would work as an int. I could not find such hence the above class.
The above pointer arithmetic works (subtraction, negation can be added similarly) and I was surprised that += works as well. Should I still add __iadd__?
Should I add/override __new__? Something like the following would not create duplicate instances for the same value:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, 'instances'):
cls.instances = {}
data = args[1]
if data in cls.instances:
return cls.instances[data]
# Create if not found:
inst = super(Hex, cls).__new__(cls) #, *args, **kwargs)
cls.instances[data] = inst
return inst
Any other suggestion to fix the class Hex or make it better?
Instead of creating a new class from scratch that holds an int (_data), why don't you simply inherit from int and override the __repr__ method?
I wouldn't go as far as optimizing for duplicated values.
class Hex(int):
def __repr__(self):
return hex(self)
Edit -
Override the methods that return a new int with a call to super() and return as a Hex object. For example -
def __add__(self, val):
return Hex(super().__add__(val))
Looks a little verbose but it works. Plus you can write a monkey patcher that takes a list of all the operations you want to override -
ops = ['__add__', '__sub__', '__mul__']
def monkey_patch(operation: str):
"""
wraps Hex() around int's magic
method for provided operation.
"""
old_op = getattr(int, operation)
new_op = lambda self, val : Hex(old_op(self, val))
setattr(Hex, operation, new_op)
for op in ops:
monkey_patch(op)
full code
This works -
>>> a = Hex(0xf)
>>> a += 1
>>> a
0x10
>>> a -= 1
>>> a
0xf
>>> a * 2
0x1e
How about inheriting from int?
>>> class Hex(int):
... def __repr__(self):
... return hex(self)
...
>>> a = Hex(123)
>>> a
0x7b
>>> a = Hex(16)
>>> a
0x10
>>> Hex(a + 2)
0x12

Perform deep copy on assignment for instances of a specific class in Python3

I have a class in Python which is little more than the primitive values, like int or float, see below
class Entry:
def __init__(self, value, timestamp):
self.value = value
self.timestamp = timestamp
def __str__(self):
return"[v= {}, ts= {}]".format(self.value, self.timestamp)
def __hash__(self):
return hash(self.timestamp)
def __eq__(self, other):
return self.timestamp == other.timestamp
def __le__(self, other):
return self.timestamp <= other.timestamp
def __lt__(self, other):
return self.timestamp < other.timestamp
def __ge__(self, other):
return self.timestamp >= other.timestamp
def __gt__(self, other):
return self.timestamp > other.timestamp
def __copy__(self):
new_entry = Entry(deepcopy(self.value), self.timestamp)
print("hi")
return new_entry
e1 = Entry("some name", 10)
e2 = e1
e2.timestamp = 20
print(e1)
I want it to behave just like the primitive types as well. So when an assignment occurs, like above, the value is deep-copied, so I don't have to think about doing it manually everywhere I do assigment like this.
As you can see, I tried overriding the __copy__ method. Unfortunely that method isn't called here. Is there another method to override? I'm pretty sure this can be accomplished in C++. Can it be done in Python too?
You can't override the = assignment operator in Python, because it isn't a "copy" operator. Instead it binds an object to a value. You can, however, use the copy module, as described here: https://docs.python.org/3/library/copy.html.

Python 3 Enums: Enum inheriting another Enum doesn't work?

I'm just trying to make an Enum in Python 3 by reference of the official Python docs https://docs.python.org/3.4/library/enum.html and specifically 8.13.13.2 and 8.13.13.4 examples.
My target is having an Enum which I can iterate, compare and also having three separate attributes. But I keep finding this error:
AttributeError: can't set attribute
It seems an error in __init__() constructor.
Code:
I tried firstly with one only class like this:
class Hand(Enum):
FIVE_OF_KIND = (6,'FIVE_OF_KIND',[5])
FOUR_OF_KIND = (5,'FOUR_OF_KIND',[4,1])
FULL_HOUSE = (4,'FULL_HOUSE',[3,2])
THREE_OF_KIND = (3,'THREE_OF_KIND',[3,1,1])
DOUBLE_PAIR = (2,'DOUBLE_PAIR',[2,2,1])
PAIR = (1,'PAIR',[2,1,1,1])
NOTHING = (0,'NOTHING',[1,1,1,1,1])
def __init__(self, val, name, struct):
self.val = val
self.name = name
self.struct = struct
def __ge__(self, other):
if self.__class__ is other.__class__:
return self.value >= other.value
return NotImplemented
def __gt__(self, other):
if self.__class__ is other.__class__:
return self.value > other.value
return NotImplemented
def __le__(self, other):
if self.__class__ is other.__class__:
return self.value <= other.value
return NotImplemented
def __lt__(self, other):
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
and secondly with two classes like this:
class OrderedEnum(Enum):
def __ge__(self, other):
if self.__class__ is other.__class__:
return self.value >= other.value
return NotImplemented
def __gt__(self, other):
if self.__class__ is other.__class__:
return self.value > other.value
return NotImplemented
def __le__(self, other):
if self.__class__ is other.__class__:
return self.value <= other.value
return NotImplemented
def __lt__(self, other):
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
class Hand(OrderedEnum):
FIVE_OF_KIND = (6,'FIVE_OF_KIND',[5])
FOUR_OF_KIND = (5,'FOUR_OF_KIND',[4,1])
FULL_HOUSE = (4,'FULL_HOUSE',[3,2])
THREE_OF_KIND = (3,'THREE_OF_KIND',[3,1,1])
DOUBLE_PAIR = (2,'DOUBLE_PAIR',[2,2,1])
PAIR = (1,'PAIR',[2,1,1,1])
NOTHING = (0,'NOTHING',[1,1,1,1,1])
def __init__(self, val, name, struct):
self.val = val
self.name = name
self.struct = struct
Enum objects already have a name attribute (for example, see 8.13.13.3), and apparently you are not allowed to set it – which makes sense when you think about how an enum should behave. You can achieve what you want like this:
from enum import Enum
class OrderedEnum(Enum):
# Same as your code.
class Hand(OrderedEnum):
FIVE_OF_KIND = (6, [5])
FOUR_OF_KIND = (5, [4,1])
FULL_HOUSE = (4, [3,2])
THREE_OF_KIND = (3, [3,1,1])
DOUBLE_PAIR = (2, [2,2,1])
PAIR = (1, [2,1,1,1])
NOTHING = (0, [1,1,1,1,1])
def __init__(self, val, struct):
# No need to set self.name. It's already handled.
self.val = val
self.struct = struct
for h in Hand:
print((h.name, h.val, h.struct))

Change representation of Python object

In Python, data types (like int, float) both represent a value, but also have some built-in attributes/functions/etc:
In [1]: a = 1.2
In [2]: a
Out[2]: 1.2
In [3]: a.is_integer()
Out[3]: False
Is it possible to reproduce this behavior within Python, e.g. define a class:
class Scalar:
def __init__(self, value)
self.value = value
# other code ....
s = Scalar(1.2)
where I could have s return 1.2 (instead of typing s.value), and do things like a = s -> a = 1.2? The closest I can get to this behavior is adding something like:
def __getitem__(self, key=None):
return self.value
and using a = s[()], but that doesn't look very good.
where I could have s return 1.2 (instead of typing s.value)
In the console? Then implement the __repr__ method.
a = s -> a = 1.2
To avoid having to use a = s.value, you can implement __call__ and call the object:
>>> class Scalar:
... def __init__(self, value):
... self.value = value
... def __repr__(self):
... return str(self.value)
... def __call__(self):
... return self.value
...
>>> s = Scalar(1.2)
>>> s
1.2
>>> a = s()
>>> a
1.2
Check the documentation about the data model on emulating numeric types.
For example:
class Scalar:
def __init__(self, value):
self.value = value
def __repr__(self):
return str(self.value)
def __call__(self):
return self.value
def __add__(self, other):
return Scalar(self.value + other.value)
def __lt__(self, other):
return self.value < other.value
def ___le__(self, other):
return self.value <= other.value
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return self.value != other.value
def __gt__(self, other):
return self.value > other.value
def __ge__(self, other):
return self.value >= other.value
Can be used like this:
>>> s1 = Scalar(1.2)
>>> s2 = Scalar(2.1)
>>> s1 + s2
3.3
>>> s1 < s2
True
>>> s1 > s2
False
>>> s1 != s2
True
>>> s1 <= s2
True
>>> s1 >= s2
False
There are also the __int__ and __float__ magic methods, which you can implement and use like this (this is more semantically correct):
>>> a = int(s)
>>> a = float(s)
As far as I know, that's not possible for your a = s example. You would have to change the behavior of =, the assignment operator. The assignment operator doesn't really do anything to the object on the right, it just copies a reference to it (in the case of an object, at least).
In general, it is possible to change the behavior of built in operators for your custom classes using operator overloading, but Python doesn't provide this sort of option for assignment (=) because of how different it is from operators like addition (+) and even equality (==).

Python magic method confusion

I've run into some confusing behaviour of the magic comparison methods.
Suppose we have the following class:
class MutNum(object):
def __init__ (self, val):
self.val = val
def setVal(self, newval):
self.val = newval
def __str__(self):
return str(self.val)
def __repr__(self):
return str(self.val)
# methods for comparison with a regular int or float:
def __eq__(self, other):
return self.val == other
def __gt__(self, other):
return self.val > other
def __lt__(self, other):
return self.val < other
def __ge__(self, other):
return self.__gt__(other) or self.__eq__(other)
def __le__(self, other):
return self.__lt__(other) or self.__eq__(other)
The class does what it is supposed to do, comparing a MutNum object to a regular int or float is no problem. However, and this is what I don't understand, it even compares fine when the magic methods are given two MutNum objects.
a = MutNum(42)
b = MutNum(3)
print(a > b) # True
print(a >= b) # True
print(a < b) # False
print(a <= b) # False
print(a == b) # False
Why does this work? Thanks.
It evaluates as follows (using a repr-like notation instead of referring to variables):
MutNum(42) > MutNum(3)
=> MutNum(42).__gt__(MutNum(3))
=> MutNum(42).val > MutNum(3)
=> 42 > MutNum(3)
And from there, it's just the int-MutNum comparision you already know works.
If you throw in some print's and/or sys.stderr.write's, I think you'll see what's happening. EG:
def __gt__(self, other):
sys.stderr.write('__gt__\n')
sys.stderr.write('{}\n'.format(type(other)))
sys.stderr.write('{} {}\n'.format(self.val, other))
result = self.val > other
sys.stderr.write('result {}\n'.format(result))
return result
def __lt__(self, other):
sys.stderr.write('__lt__\n')
sys.stderr.write('{}\n'.format(type(other)))
sys.stderr.write('{} {}\n'.format(self.val, other))
result = self.val < other
sys.stderr.write('result {}\n'.format(result))
return result
When you try to compare self.val (an int) to other (a MutNum), python realizes it has nothing for comparing an int to a MutNum, and reverses the order of the comparison, and compares a MutNum to an int - which is something you've defined. That is, a single > comparison is doing the > as you'd expect, but it's also doing a <.

Categories

Resources