[PYTHON] I made an extenum package that extends an enum

background

The enum module has been added from Python 3.4. A package backported for versions lower than 3.4 (including 2.x) is registered with PyPI as enum34.

If you have never used the enum module, the following article will be easy to understand.

A note by Nick Coghlan, the core developer of CPython, gives a background on the enum standard library and a summary of the discussions at the time.

Python enums may be satisfactory with * Good Enough * as they weren't originally standard.

On the contrary, what is missing? There is a book called Effective Java, which is like a bible in the Java world, and covers one chapter about enum best practices. I will.

In the Java world, enums are often used because they have a wide range of applications and are useful features such as guaranteeing type safety at compile time and implementing singletons. So, while looking at Effective Java, I tried to extend the functions that Python enum does not have.

extenum

I have created an extenum package that extends the standard enum module.

Installation method

$ pip install extenum

the term

According to Terminology of official document

>> from enum import Enum
>> class Color(Enum):

... red = 1 ... green = 2 ... blue = 3 ...

 > Annotation terms
 > * The class `Color` is an enum (or Enum).
 > * Attributes `Color.red`,` Color.green`, and others are enum members (or Enum members).
 > * Members of enums have names and values (`Color.red` has a name of` red`, `Color.blue` has a value of 3, etc.).

 The constants defined in the enumeration are defined as *** enumeration member (or Enum member) ***. In other languages, it is also called an enumerator or enumeration constant. I think that enumerated constants are easier to understand because I expect them to play a role as constants, but in this article, I will call enumerated constants members of enumerated types according to the official documentation.

### Constant fixed method implementation
 Effective Java is named as a constant, but this * constant * refers to a member of the enumeration.

 The reason I decided to make extenum was that this mechanism was not available. When I searched for constant fixed methods, I found [asym-enum](https://github.com/AsymmetricVentures/asym-enum#methods-and-functions) as something like that.


#### **`3.4`**
```python

from asymm_enum.enum import Enum

class E(Enum):
    A = 1
    B = 2

    def get_label(self):
        ''' instance methods are attached to individual members '''
        return self.label

    @classmethod
    def get_b_label(cls):
        ''' class methods are attached to the enum '''
        return cls.B.label

I thought this was a little ugly and decided to make it myself.

With extenum, you can implement it like this:

constant_specific_enum.py


from extenum import ConstantSpecificEnum

class Operation(ConstantSpecificEnum):
    PLUS = '+'
    MINUS = '-'
    TIMES = '*'
    DIVIDE = '/'

    @overload(PLUS)
    def apply(self, x, y):
        return x + y

    @overload(MINUS)
    def apply(self, x, y):
        return x - y

    @overload(TIMES)
    def apply(self, x, y):
        return x * y

    @overload(DIVIDE)
    def apply(self, x, y):
        return x / y

I was able to implement it neatly.

Since Python does not have an overload mechanism, * @ overload (CONSTANT) * implement a constant fixed method with a decorator.

3.4


>>> from constant_specific_enum import Operation
>>> for name, const in Operation.__members__.items():
...     print(name, ':', const.apply(2, 4))
PLUS : 6
MINUS : -2
TIMES : 8
DIVIDE : 0.5

Fixed constant methods are also useful for communicating intent by the name of the method. For example, let's look at an enum that holds processing status information.

3.4


from extenum import ConstantSpecificEnum

class Status(ConstantSpecificEnum):
    PREPARING = 1
    WAITING = 2
    RUNNING = 3

    @overload(PREPARING)
    def is_cancelable(self):
        return False

    @overload(WAITING)
    def is_cancelable(self):
        return True

    @overload(RUNNING)
    def is_cancelable(self):
        return True

Suppose each enum member points to a status value, but you need to determine if it's a cancelable status before you can cancel this process.

If you don't have a fixed constant method, you're checking to see if the enum member passed to a function is either * WAITING * or * RUNNING *.

3.4


def cancel(status):
    if status in [Status.WAITING, Status.RUNNING]:
        # do cancel

This requires the implementer or reader of the cancel function to know the details of the status information enum, and may need to modify the conditions in this function as the status information grows.

If you have a fixed constant method, the status judgment of the preprocessing of this cancel function can be written more naturally and neatly.

3.4


def cancel(status):
    if status.is_cancelable():
        # do cancel

Compared to the code above, it is easier for implementers and readers of the cancel function to understand, and even if the status information increases, it is only necessary to add an enumerated constant fixed method.

Strategy enum pattern

This is an application example of constant fixed method implementation. It is intended for a more maintainable implementation by nesting enums of day of the week and weekday / holiday attributes.

strategy_enum_pattern.py


from extenum import ConstantSpecificEnum

class PayrollDay(ConstantSpecificEnum):

    class PayType(ConstantSpecificEnum):
        WEEKDAY = 1
        WEEKEND = 2

        @overload(WEEKDAY)
        def overtime_pay(self, hours, pay_rate):
            return 0 if hours <= 8 else (hours - 8) * pay_rate / 2

        @overload(WEEKEND)
        def overtime_pay(self, hours, pay_rate):
            return hours * pay_rate / 2

        def pay(self, hours_worked, pay_rate):
            base_pay = hours_worked * pay_rate
            overtime_pay = self.overtime_pay(hours_worked, pay_rate)
            return base_pay + overtime_pay

    MONDAY = PayType.WEEKDAY
    TUESDAY = PayType.WEEKDAY
    WEDNESDAY = PayType.WEEKDAY
    THURSDAY = PayType.WEEKDAY
    FRIDAY = PayType.WEEKDAY
    SATURDAY = PayType.WEEKEND
    SUNDAY = PayType.WEEKEND

    def pay(self, hours_worked, pay_rate):
        return self.value.pay(hours_worked, pay_rate)

Java is useful as a practice, but Python feels overkill (subjective). I tried to implement it, but I feel that it is a subtle example that can not be said to be good or bad.

Please see to the extent that you can do something like this.

3.4


>>> from strategy_enum_pattern import PayrollDay
>>> PayrollDay.MONDAY.pay(10, 1000.0)
11000.0
>>> PayrollDay.WEDNESDAY.pay(8, 1000.0)
8000.0
>>> PayrollDay.SATURDAY.pay(10, 1000.0)
15000.0
>>> PayrollDay.SUNDAY.pay(8, 1000.0)
12000.0

Implicit enum member

Since the members of the enumeration type define class variables, the following enumeration type cannot be defined.

3.4


class Color(Enum):
    red, green, blue

When I run it, I get * NameError *.

3.4


NameError: name 'red' is not defined

Added enum module [PEP 435 -Not having to specify values for enums-](https://www.python.org/dev/peps/pep-0435/#not-having-to-specify-values-for- According to enums)

Cons: involves much magic in the implementation, which makes even the definition of such enums baffling when first seen. Besides, explicit is better than implicit.

Reasons why it is not possible to define an implicit value are that the implementation is magical, confusing when first seen, and more contrary to explicit Zen than implicit.

This is correct for Python culture, so it's okay, but Nick Coghlan's note Support for alternate declaration syntaxes enum_creation.html # support-for-alternate-declaration-syntaxes) mentions how to implement implicit enum members.

Implicit enums that don’t really support normal code execution in the class body, and allow the above to be simplified further. It’s another variant of the autonumbered example in the test suite, but one that diverges substantially from normal Python semantics: merely mentioning a name will create a new reference to that name. While there are a number of ways to get into trouble when doing this, the basic concept would be to modify __prepare__ on the metaclass to return a namespace that implements __missing__ as returning a custom sentinel value and overrides __getitem__ to treat repeating a name as an error:

Python ~~ Black Magic ~~ It's an interesting method for metaprogramming, so I implemented it.

The special method __prepare__ was added from Python 3 and the metaclass is initialized. Hooks the process of returning the namespace of that class when __missing__ hooks KeyError error handling to dictionary subclasses when the key does not exist in the dictionary To do.

You can hook the namespace definition of a class by returning a dictionary with __missing__ as a namespace object in the previous __prepare__.

I don't know what you're talking about, so let's code it.

prepare_missing.py


class MyNameSpace(dict):
    def __missing__(self, key):
        self[key] = value = 'x'
        return value

class MyMetaclass(type):
    @classmethod
    def __prepare__(metacls, cls, bases):
        return MyNameSpace()

    def __new__(metacls, cls, bases, classdict):
        print('classdict is', classdict.__class__)
        return super().__new__(metacls, cls, bases, classdict)

Use this metaclass to define your class.

3.4


>>> from prepare_missing import MyMetaclass
>>> class Color(metaclass=MyMetaclass):
...     red, green, blue
... 
classdict is <class 'status.MyNameSpace'>
>>> Color.red
'x'
>>> Color.blue
'x'

When defining a class, the __new__ method of the metaclass * MyMetaclass * is called and * MyNameSpace * is passed to the class namespace (* classdict *). The * MyNameSpace * __missing__ sets the string * x * for names that do not have a key, that is, they do not exist in the class namespace.

3.4


>>> from extenum import ImplicitEnum
>>> class Color(ImplicitEnum):
...     RED
...     GREEN
...     BLUE
...
>>> for name, const in Color.__members__.items():
...     print(name, ':', const.value)
...
RED : 1
GREEN : 2
BLUE : 3

As an aside, the * @ overload * decorator of * ConstantSpecificEnum * in the previous section is also implemented using this mechanism. I think there are pros and cons to hiding this decorator definition itself, but even if it is hidden, it feels strange from the user side so that * @ classmethod * and * @ staticmethod * can be handled globally. I wondered if there was one.

EnumSet An EnumSet is like a utility class that produces a set containing Enum constants. I implemented it while looking at Java's EnumSet.

If you use EnumSet to implement processing such as flag operation, which was implemented by bit operation for easy understanding, it will be easy for people to feel.

3.4


>>> from enum import Enum
>>> class Mode(Enum):
...     READ = 4
...     WRITE = 2
...     EXECUTE = 1
... 

Let's try using this * Mode * enum.

3.4


>>> from extenum import EnumSet

>>> EnumSet.all_of(Mode)  #Generate an EnumSet containing all Mode enum members
EnumSet({<Mode.READ: 4>, <Mode.WRITE: 2>, <Mode.EXECUTE: 1>})

>>> EnumSet.none_of(Mode)  #Generate an empty EnumSet that takes an enum member of Mode
EnumSet()

>>> enumset = EnumSet.of(Mode.READ, Mode.EXECUTE)  #Generate an EnumSet containing any enum member
>>> enumset
EnumSet({<Mode.READ: 4>, <Mode.EXECUTE: 1>})
>>> enumset.update(EnumSet.of(Mode.READ, Mode.WRITE))
>>> enumset
EnumSet({<Mode.READ: 4>, <Mode.WRITE: 2>, <Mode.EXECUTE: 1>})
>>> Mode.WRITE in enumset
True
>>> 2 in enumset
False

Putting them together in an EnumSet makes it easier to handle modes and options.

Summary

The following features are provided by extenum as a Java-like enum extension.

It inherits and extends the standard * Enum *, * EnumMeta *, and * _EnumDict *, so it probably works straightforwardly as a regular enum.

Please let us know if there are any other enum practices that might be interesting. Sometimes the purpose is to make it rather than use it.

Recommended Posts

I made an extenum package that extends an enum
I made an Ansible-installer
I made an Android application that displays Google Map
I made an Xubuntu server.
I made an anomaly detection model that works on iOS
I made an Anpanman painter discriminator
I made an Angular starter kit
I made a package that can compare morphological analyzers with Python
I made an AI that crops an image nicely using Salience Map
I made something that moves (wider range)
I made an online frequency analysis app
I made an alternative module for japandas.DataReader
I made a Chrome extension that displays a graph on an AMeDAS page
I made an AI that predicts from trivia and made me infer my trivia. Hee-AI
With LINEBot, I made an app that informs me of the "bus time"
I made a package to create an executable file from Hy source code
Obstacle (black) I made an automatic avoidance caterpillar.
I made an Amazon Web Service Dash button
I got an error that Python couldn't read settings.ini
Story that an inexperienced person made a masked solver
I made a VM that runs OpenCV for Python
I made an action to automatically format python code
I made an app that warns me if I mess with my smartphone while studying with OpenCV
I made an IFTTT button that unlocks the entrance 2 lock sesame with 1 button (via AWS Lambda)
I made an API with Docker that returns the predicted value of the machine learning model