Silas Ray
Silas Ray

Reputation: 26160

Use a decorator (or some other pattern) on an object instance to generate a class

I can see multiple roadblocks to making this work, and it may well be impossible, but I thought I'd at least ask...

So, I have a class AbstractEnumeratedConstantsGroup that, unsurprisingly, when subclassed creates an object that represents a group of enumerated constants (ex FLAVORS with values ('VANILLA', 'CHOCOLATE', 'STRAWBERRY') where FLAVORS[0] returns 'VANILLA', FLAVORS[1] returns 'CHOCOLATE', FLAVORS.VANILLA returns 0, FLAVORS.CHOCOLATE returns 1, etc) it uses a metaclass and some other footwork so that all that is required to create a new constants group is:

class FLAVORS(AbstractEnumeratedConstantsGroup):
    _constants = ('VANILLA', 'CHOCOLATE', 'STRAWBERRY')
FLAVORS = FLAVORS()

I'd like to reduce it further to:

@enumerated_constants_group
FLAVORS = ('VANILLA', 'CHOCOLATE', 'STRAWBERRY')

The problems I don't know how to get past are:

    1. No good way to get the label name for the object from the defining scope from the decorator code 2. Can't decorate something that isn't a class, method, or function

I've considered using a factory/builder, but what I don't like about that is that it requires duplication of the group name in the code, or that the groups have unhelpful __name__ values (which are used in __repr__ and __str__). Ex:

FLAVOR = enumerated_constants_group('FLAVOR', ('VANILLA', 'CHOCOLATE', 'STRAWBERRY'))

or

FLAVOR = enumerated_constants_group(('VANILLA', 'CHOCOLATE', 'STRAWBERRY'))
# FLAVOR.__name__ becomes some unfriendly machine-generated string

As an alternative to the decorator idea, would it be possible to reference the calling scope (through introspection or from implicitly passing it to the function) from a factory method such that calling the method would insert the newly created object in to the calling namespace with the given name? Ex:

enumerated_constants_group('FLAVOR', ('VANILLA', 'CHOCOLATE', 'STRAWBERRY'))
# I could now reference FLAVOR in any arbitrary scope that the method was called from

Is there anything I can do to achieve what I want? Are there other approaches I haven't thought of?

Upvotes: 3

Views: 102

Answers (5)

thebjorn
thebjorn

Reputation: 27321

Have you looked at http://code.activestate.com/recipes/413486/

It allows you to write code like this:

>>> from enum import enum
>>> FLAVORS = enum('VANILLA', 'CHOCOLATE', 'STRAWBERRY')
>>> print FLAVORS
enum (VANILLA, CHOCOLATE, STRAWBERRY)
>>> print FLAVORS.VANILLA
VANILLA
>>> f = FLAVORS.CHOCOLATE
>>> f in FLAVORS
True
>>> for f in FLAVORS:
...     print f
...
VANILLA
CHOCOLATE
STRAWBERRY

Upvotes: 2

gepatino
gepatino

Reputation: 168

Besides function/method decorators, you have also class decorators (at least they were there in python 2.4), see http://www.python.org/dev/peps/pep-3129/

Anyway, I'm not sure this is what you need.

I would go with the factory class that gets (type, values) as args.

One note: if your code is just like this, and AbstractEnumeratedConstantsGroup doesn't add any functionality, why don't you just use tuples?

You could define a constants.py module with the following content:

FLAVOURS = ('VANILLA', 'CHOCOLATE', 'STRAWBERRY')
PETS = ('DOG', 'CAT', 'FISH')

and then use those tuples from your code:

import constants
for pet in constants.PETS:
    print 'I have a ', pet

Upvotes: 0

chepner
chepner

Reputation: 531798

As an intermediate step, you could do

FLAVORS = type("FLAVORS",
               (AbstractEnumeratedConstantsGroup,),
               {_constants = ('VANILLA', 'CHOCOLATE', 'STRAWBERRY')})()

which leads to a fake decorator

def enumerated_constants_group(*args):
    AECG=AbstractEnumeratedConstantsGroup
    t = type(args[0], (AECG,), dict(_constants=args[1:]))
    return t()

FLAVORS = enumerated_constants_group('FLAVORS', 'VANILLA', 'CHOCOLATE', 'STRAWBERRY')

Upvotes: 0

BrenBarn
BrenBarn

Reputation: 251438

There is no general way to affect what happens when you assign to a bare name (as in FLAVORS = ('VANILLA', 'CHOCOLATE', 'STRAWBERRY')). So what you want is not possible.

That said, you could write a metaclass that replaces the returned class with an instance of itself, so that you don't need your last FLAVORS = FLAVORS(...) line in your first example. The class definition itself would suffice to create the object.

Upvotes: 3

Related Questions