Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Porting C defines the Pythonic way

tl;dr What's a Pythonic (Py 3) way of specifying a large number of defined bit masks and constants? Use an Enum(s) or just have a load of consts as class variables? And the advantages/disadvantages?

Background

I'm porting C code to Python 3. In the C code there's a large number of defines which are used as bit masks:

#define ERR_1 = 0x8
#define ERR_2 = 0x2
#define ERR_4 = 0x100
...

I thought in Python a Pythonic way of having these would be by using an Enum, I came across IntEnum which means I don't have to use .value everywhere like I would with a normal Enum:

from enum import IntEnum

class Errors(IntEnum):
    BROKEN = 0x8
    FUBARED = 0x4
    GIVEUP = 0x7

print(0xFF & Errors.BROKEN)

but it's still more verbose than just having print(0xFF & ERR_1) which I could get if I had them all as consts.

like image 571
Peanut Avatar asked Aug 25 '15 18:08

Peanut


3 Answers

I think using enum is more pythonic - there was a whole lot of effort put into creating that module, and not without a reason.

"Standard" way of things was using constants (like ERR_1) but this is ugly, buggy and really hard to mantain. This is why that module was developed.

Now that I've stated plain answer, I can also propose that you use both at the same time:

ERR_1 = 0x8
ERR_2 = 0x4
ERR_3 = 0x7

class Errors(IntEnum):
    BROKEN = ERR_1
    FUBARED = ERR_2
    GIVEUP = ERR_3

print(0xFF & Errors.BROKEN)
print(0xFF & ERR_1)

I think it's ugly, but you were torn between two options, so I wanted to show you how to make them one.

Also worth reading:

  • why use enum at all? - it says "Java", but this is programming in general, not language-depentent issue
  • PEP about that module - you can see how many people thought that through and how thorough they were
  • not "which is more pythonic" but more "how to do it" - with some arguments for using enum module
  • how were they thinking? - "they" meaning "developers of that module"
like image 96
Filip Malczak Avatar answered Oct 21 '22 20:10

Filip Malczak


It all depends.

If the constants are logically integers (you do arithmetic, bitwise logic, etc. with them), they should be regular global variables. Flag values fall here, as with the various flags for os.open(), but if you're just specifying a constant to indicate which operation to perform, an enum is more appropriate (or better yet, multiple driver functions).

Enumerated types are normally used when you have a (relatively) small set of values, and those values are mostly only compared to one another (e.g. with if x is MyEnum.FIRST... elif x is MyEnum.SECOND etc.). In this case, you usually do not need IntEnum because you shouldn't be using .value very often to begin with. Indeed, IntEnum is mostly a backwards-compatibility hack.

like image 1
Kevin Avatar answered Oct 21 '22 20:10

Kevin


Using an Enum is definitely the way to go, and you can even have the enum members at the module scope quite easily:

class Errors(IntEnum):
    BROKEN = 0x8
    FUBARED = 0x4
    GIVEUP = 0x7
globals().update(Errors.__members__)

so your comparison becomes:

print(0xFF & BROKEN)

About the only time it makes sense to not use Enum is when you have several names that aren't really duplicates but map to the same value, as the duplicate values will all map to the same name:

class Bad(Enum):
    BROKEN = 0x01
    MISSING = 0x01
    BLUE = 0x01

But if you have distinct names for unique values Enum is the way to go.

like image 1
Ethan Furman Avatar answered Oct 21 '22 20:10

Ethan Furman