简体   繁体   中英

In Python, how to initialize a class attribute from the return value of a class method?

Imagine we have this class:

class Custom:
    @classmethod
    def get_choices(cls):
        # use cls attributes to return a list

    mapping = {"key": ... }

I would like to associate the value returned by get_choices() to key . What code should go instead of the placeholder ... ?


EDIT: I wanted to keep the question context-agnostic (it seemed to me that this requirement was common enough, but I could be biased). As was suggested in comments, I will give some more details, because it seems we share the concern of 'most straightforward code':

I am working on a Django application, where I want to keep enumeration of values separated of the Model s using them. What seemed to me like a natural solution was to create an enumeration.py module, where I could then define a class for each enumeration (the class having at least a class method to generate the collection of values). This is the get_choices() in the example.

Now, because of business logic, I need to map those choices to a key. This mapping being logically coupled to the enumeration, it seemed like a good structure to keep them together, in the same class (giving client code uniform and explicit access, being prefixed by the class name).

You cannot do it in class definition because class object has not been created yet. After the class definition, you could definitely do something like this -

Custom.mapping['key'] = Custom.get_choices()

Although the recommended way to do this would be to use a metaclass.

class CustomMetaClass(type):

      def __init__(cls, classname, bases, attrs):
          super(CustomMetaClass, cls).__init__(classname, bases, attrs)

          cls.mapping['key'] = cls.get_choices()

class Custom(metaclass = CustomMetaClass): # assuming you are using python 3

      mapping = {}

      @classmethod
      def get_choices(cls):
          # add your custom code here
          pass

With that said, that is an Object Oriented solution to the problem. you can ofcourse use some function to generate choices thus ending the need to use metaclass.

For Edited Question:-

In that case, I think you should just maintain a file named 'choices.py' as you yourself suggested and use them in your mapping, instead of get_choices class method. You don't need to unnecessarily make classes for each model if all you want to do is store choices. Just use dicts and constants.

If your classes needs to be generated dynamically, say from db, then you need to create separate model for storing choices.

class CustomModelChoices(models.Model):

      model_name = models.StringField(db_field = 'mn')
      choices = models.DictField(db_field = 'ch')

class CustomModel(models.Model):

     _choice_generator = CustomModelChoices

     mapping = {
         'key': CustomModelChoices.objects.get(model_name = 'CustomModel').choices
     }

This is just a raw design, might need to be improved a lot, but something on these lines.

If the logic you need to run is trivial then just putting the logic directly after the class is fine:

class Custom1:
    value = 1
    @classmethod
    def get_choices(cls):
        return [cls.value]
Custom1.mapping = {"key": Custom1.get_choices()}

If the logic is more complicated, and especially if needed by multiple classes, then a decorator could be useful. eg

def add_mapping_choices(cls):
    cls.mapping = {"key": cls.get_choices()}
    return cls

@add_mapping_choices
class Custom2:
    value = 2
    @classmethod
    def get_choices(cls):
        return [cls.value]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM