在Python中,如何从类方法的返回值初始化类属性?

3

想象一下我们有这个类:

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

    mapping = {"key": ... }

我想将get_choices()返回的值与key关联起来。在占位符...中应该放什么代码?
编辑:我希望保持问题的普适性(我觉得这个要求很常见,但我可能有偏见)。如评论所建议的,我会给出更多细节,因为我们似乎都担心“最简单的代码”:
我正在开发一个Django应用程序,我想将值的枚举分离出使用它们的Models。对我而言,自然的解决方案是创建一个enumeration.py模块,在其中可以为每个枚举定义一个class(该类至少具有一个类方法以生成值的集合)。这就是示例中的get_choices()
现在,由于业务逻辑,我需要将这些选择映射到一个键上。这种映射与枚举逻辑上耦合在一起,将它们保持在同一个类中似乎是一个很好的结构(使客户端代码具有统一和明确的访问方式,并以类名为前缀)。

1
你不能在类内部这样做,@classmethod 直到类定义完成后才能被实际调用。 - jonrsharpe
1
@jonrsharpe 谁会想到C++竟然提供了Python所没有的便利呢 ;) - Ad N
那似乎不太可能!我昨天回答了一个相关的问题,你可能会发现我的答案有用:http://stackoverflow.com/a/32069776/3001761 - jonrsharpe
2个回答

6
由于类对象尚未创建,因此您不能在类定义中执行此操作。在类定义之后,您可以执行以下操作:
Custom.mapping['key'] = Custom.get_choices()

虽然推荐的做法是使用元类,但也可以通过其他方式实现。
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

话虽如此,这是一个面向对象的解决方案。你当然可以使用一些函数来生成选择项,从而不再需要使用元类。

对于编辑后的问题:

在这种情况下,我认为你应该像你自己建议的那样维护一个名为“choices.py”的文件,并在映射中使用它们,而不是使用get_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
     }

这只是一个初步的设计,可能需要大量改进,但基本方向已经确定。

请不要为这样的事情使用元类。已经有太多的元类存在了。 - Sven Marnach
稍微解释一下,使用元类会导致代码变得冗长且有些晦涩,而实际上只是做一些非常简单的事情。在几乎所有情况下,通过稍微重新排列一下代码,就可以找到一个非常简单和直接的解决方案,从而使代码更易读。如何做取决于许多因素,我们需要更多的上下文来建议替代设计。当然,元类也有有效的用例,但我看到的绝大多数实际用例都是过度设计的。 - Sven Marnach
1
我知道元类的所有优缺点。如果你没有注意到,我也提供了一行解决方案。但是在我看来,像那样在类外修改类并不是一个太优雅的解决方案。我认为用户需要找到另一种解决方法来生成映射,但如果他只想存储类方法的结果,那么这并不是太糟糕的选择。 - hspandher
@SvenMarnach 对不起,我没有提供上下文(我想这里是无关紧要的)。我编辑了问题,解释了一下我的意图,这些意图是由清晰的代码驱动的,对我来说同样重要的是良好结构化的代码 :) - Ad N
关于您的编辑:我推断“them”指的是选项集合?如果是这样,该集合可能在运行时由get_choices()生成,不一定是静态列表。 - Ad N

0
如果你需要运行的逻辑很简单,那么直接将逻辑放在类后面就可以了。
class Custom1:
    value = 1
    @classmethod
    def get_choices(cls):
        return [cls.value]
Custom1.mapping = {"key": Custom1.get_choices()}

如果逻辑更加复杂,尤其是需要多个类使用时,装饰器可能会很有用。例如:
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]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接