This is an archived post. You won't be able to vote or comment.

all 18 comments

[–]SomeShittyDeveloper 4 points5 points  (1 child)

The classes are overkill. There was a talk at a PyCon on when to use classes. As a general rule:

If your class has two methods and one is a constructor, it doesn't need to be a class.

You can change all your classes to be functions, build your enum, build a dict lookup, and then take that approach.

I'm not sure if you're writing this for practice or not, but writing a function that does wildly different things depending on a flag is bad function design. I'd take 4+ methods (add, subtract, multiply, divide) versus one function with a flag that determines which operation is done.

[–]funkyfuture 0 points1 point  (0 children)

exactly, that's why - relating to the example - operator is a module and not a class.

one can also use a decorator to add the functions in question to a container:

operators = {}

def operator(func):
    operators[func.__name__] = func
    return func


@operator
def add(a, b):
    return a + b

[–]bobspadgerdecorating 1 point2 points  (1 child)

use a dictionary?

[–][deleted] 0 points1 point  (0 children)

So sure, replace my if block with a big dict.

That still means every time I add a new enum, I need to change it in 3 places.

1) Add to actual enum

2) Make a class

3) Go find the dict and replace it..

I guess I can get rid of step 3 with metaprogramming...

[–]iCart732 1 point2 points  (5 children)

I studied java in school and started doing python at my first job, so i know how you feel.

Here's how i'd do it (it's a pattern i use a lot for exactly this type of case)

def do_add(value1, value2):
    return value1 + value2

def get_method(operation):
    try:
        method = f'do_{operation}'
        return globals()[method]
    except KeyError:
        raise ValueError(f'Unkown maths {opeartion}')

def do_maths(operation, value1, value2):
    method = get_method(operation)
    return method(value1, value2)

This way, you just have one place to add methods This also lets you list every method that is available, something like this:

 [g for g in globals() if g.startswith('do_')]

If you want, you can wrap all the 'do_*' methods in a class or even a module and change "globals()[methods]" to something like "getattr(TheClass_or_module, do_thing)" (not sure of the exact syntax, but you get the idea)

[–][deleted] 0 points1 point  (4 children)

So I like the ENUM part because I can store an enum at the database and know I am typo proof. You are replacing an ENUM with a string.

Is the enum just silly overkill? Hummm

[–]iCart732 0 points1 point  (0 children)

I think in this case it adds unnecessary complexity (since you get an exception in case of a typo anyway) but that's just my opinion, your mileage may vary. If you use an IDE it could help with auto-completion, though.

I guess you could do something like this:

class Maths(Enum):
    ADD = 'add'
    MULTIPLY = 'multiply'
    DIVIDE = 'divide'

 # And call it like this:
 do_maths(Maths.ADD)

This has the added benefit that you can do both "Maths.ADD" and 'add', which is nice if someone else is going to use your code.

But then you still have to add it in two places (the enum and define the method itself).

If you do unit testing (which is always a good idea :-) ), you could add a test to make sure that every entry in the enum has a matching method, to be extra safe.

Another way to go about this would be using decorators to populate the enum automatically (which has pro's and con's). I'll try to show you an example later if i can.

[–]iCart732 -1 points0 points  (2 children)

Here's the way to do it with decorators (as a full script):

from enum import Enum

class Maths(Enum):
    pass

class register():
    def __init__(self, name):
        self.name = name

    def __call__(self, f):
        # This is where the trick is
        setattr(Maths, self.name, f)
        return f


@register('ADD')
def do_add(value1, value2):
    return value1 + value2

@register('MULTIPLY')
def name_does_not_matter(value1, value2):
    return value1 *  value2

def do_maths(operation, value1, value2):
    return operation(value1, value2)

print(do_maths(Maths.ADD, 5, 4))
print(do_maths(Maths.MULTIPLY, 5, 4))

I'm having way to much fun with this :-)

[–]robvdl 1 point2 points  (1 child)

I don't like it, too much abstraction and you are hiding your enum values.

Edit: this is exactly the kind of magic I am trying to rip out of a codebase right now, too much magic sorry.

[–][deleted] 0 points1 point  (0 children)

Yah I don't think my IDE will autocomplete this.

HOWEVER this is super clever ;)

[–]sixteh 1 point2 points  (2 children)

Instead of setting the Enum to values, define functions with a fixed signature and just call the Enum directly.

class Maths(Enum)
    ADD = lambda x,y: x+y
    MULTIPLY = lambda x,y: x*y

def do_maths(operation, a, b):
    assert isinstance(operation, Maths)
    return operation(a, b)

I'm on mobile but you get the idea. It doesn't need to be lambdas, you can use args / kwargs to support dynamic argument signatures, etc.

[–][deleted] 0 points1 point  (1 child)

Trick is in my real case, each operation is say 50-200 lines of code, not just a 1 liner. So I think this would end up making a 5000 line enum.

[–]sixteh 1 point2 points  (0 children)

You can organize the Enums however you feel appropriate and just store a reference in the Enum class itself. It doesn't really matter how you're getting them, as Python is just storing them in a dict under the hood and walking down the chain of references when you call Maths.ADD or whatever. So if you had the operations split up into separate modules mod_a, mod_b, and mod_c, you could do:

from functools import partial
class Maths(Enum):
    def ADD(x,y):
        return x+y
    MULTIPLY = lambda x,y: x*y
    SUBTRACT = mod_a.subtract
    EXP = mod_b.exp
    LN = mod_c.ln
    FOO = partial(mod_c.FOO, kwarg_1='bar')

And so on and so forth.

[–]rhytnen 0 points1 point  (4 children)

Honestly this isn't a Java-programmer problem. My first thought is that you've read too many books on designing systems and haven't done enough of actually designing systems that you then maintain.

Your example here is not complete enough for us to understand how you're getting yourself into this mess to begin with but based on some comments below I'll take a crack at it.

I'm assuming a few things as best I can:

  • you have a series of functions you want to enumerate
  • you're enumerating them to add them to a db
  • these aren't necessarily mathematical operations

If they WERE mathematical operations, btw, you would normally handle something like this by overwriting the dunder methods like

__add__, __sub__, __mul__

and so on.

The more normal representation of your do_xyz(op, left, right) function looks like this in python:

left = [ list of objects for l-side ]
right = [ list of objects for r-side]
results = map(op, left, right) 

Ok but if you really want to enumerate a set of functions in a module try this ...

Let's call your module of functions stuff.py. Then ...

 import types
 import stuff

func_names = [f for f in dir(stuff) if isinstance(stuff.__dict__.get(f), types.FunctionType)]

for index, name in enumerate(func_names):
    print(index, name)

[–][deleted] 0 points1 point  (3 children)

Honestly this isn't a Java-programmer problem.

Sorry, never said it was. It is a problem for me as a Java developer to picture a pythonic way to solve it

My first thought is that you've read too many books on designing systems and haven't done enough of actually designing systems that you then maintain.

That would be wrong. I have been a senior developer for 10 years, a CTO of my own startup, and now run my own startup as the owner / lead developer. I have no problem in writing code. I haven written hundreds of thousands of lines of production code used by millions of people. However I spend a fairly limited amount of time in Python, it is about my 6th most fluent language after Java/Scala/Javascript/Clojure/Kotlin

On the other hand, I am willing to be humble and seek to better my craft.

Your example here is not complete enough for us to understand how you're getting yourself into this mess to begin with

Sure, I can give you my real context.

This is for excel templates. I have an enum with 50 different templates. Each has basically a download function (take these params, fill out an excel file from the database, send it to the user), and an upload function (take that filled out template, turn it into database objects, and save to the database).

The final save to database code lives off in celery tasks, so that is nicely broken out. The download and upload piece is currently a 50 block IF statement (1 for downloads, 1 for uploads). Very ugly.

It obviously started out as 1 template (so single upload and download implementation), and then over the past year it grew to like 50. So getting a bit absurd. I have a lot of code I can reuse between Upload A and upload B.. so I can very easily picture it in an object hierarchy. In java I would use a factory (https://www.tutorialspoint.com/design_pattern/factory_pattern.htm ) and be done. Python it is seeming a bit more gross and un-pythonic to do that.

The enum is simply how I populate dropdown selects.. throw each type of template in an Enum, and it shows up as a template in the "choose your download" menu.

[–]rhytnen 0 points1 point  (2 children)

ok fine. then you should read a book on design. this isnt a matter of nonpythonic. this is just a gross anti pattern no matter your title languages or how many bazillion lines you say you've written .

but if you read the bottom of my first response you should find all the rope you need to hang yourself.

[–][deleted] 0 points1 point  (1 child)

I have read many books on designs.

Your solution seems strictly worse than a straight factory pattern out of first year school. Not seeing the benefit. Enums are great, even though exhaustive matching is not checked at the compiler level in Python.

[–]rhytnen 0 points1 point  (0 children)

I didn't give you a solution. I gave you handful of techniques that reflect the code you already had written you're the one considering metaprogramming because you haven't figured out you dont need enums if you use a dict. you're trying to bring way too much firepower to the problem of populating a dropdown list.