【问题标题】:Associating properties to Class objects将属性关联到类对象
【发布时间】:2011-04-05 08:23:13
【问题描述】:

我想为类定义属性,并且能够在我实际实例化该类的对象之前访问它们。

我会给出一些背景信息。我的应用程序处理一个组件库。每个组件都映射到一个 Python 类。现在我想在实际实例化类之前知道组件需要什么配置。

一种解决方案是这样写:

class Component:
    @classmethod
    def config(cls, name, description, default=None):
        ''' Define one configuration switch for the class. '''
        # now put this information in a class-specific dictionary

class Model1(Component):
    @classmethod
    def define_configuration(cls):
        cls.config('n', 'number of burzs to instigate')
        cls.config('skip', 'skip any barzs among the burzs', default=True)
    # ...

component_class = Model1
component_class.define_configuration()

但是,它看起来很丑陋。理想情况下,我希望能够编写如下内容,并且仍然能够将配置开关放在特定于类的字典中以供以后访问。

class Model1(Component):
    config('n', 'number of burz to instigate')
    config('skip', 'skip any barz in the data', default=True)

我最初的解决方案是这样写:

class Model1(Component):
    Model1.config('n', 'number of burz to instigate')
    Model1.config('skip', 'skip any barz in the data', default=True)

但是我在 SO 上的其他问题上发现,执行主体时尚未定义类名。

我该怎么办?

tl;dr:我怎样才能获得一个很好的语法来定义特定于类的属性(在我实例化该类的对象之前)?


这是(作为记录)建议的解决方案(有点详细)。 耶!我可以得到我想要的。 :-)

from collections import namedtuple
Config = namedtuple('Config', 'name desc default')

def config(name, description, default=None):
    ComponentMeta.tmp_config_storage.append(Config(name, description, default))

class ComponentMeta(type):
    tmp_config_storage = []
    def __init__(cls, clsname, bases, clsdict):
        for config in ComponentMeta.tmp_config_storage:
            if not 'my_config' in cls.__dict__:
                setattr(cls, 'my_config', [])
            cls.my_config.append(config)
        ComponentMeta.tmp_config_storage = []

class Component(object):
    __metaclass__ = ComponentMeta

class Model1(Component):
    config('x1', 'for model1')
    config('y1', 'for model1')

class Model2(Component):
    config('x2', 'for model2')

print 'config for Model1:', Model1.my_config
print 'config for Model2:', Model2.my_config

【问题讨论】:

  • 我很确定您需要在其中某处使用self。但我不明白你为什么对 config 方法使用两次调用?
  • @vlad 他使用了两个调用,因为他想添加两个项目。每个电话加一个。此外,您看到的是 cls 而不是 self,因为这些是在类而不是实例上操作的类方法。
  • 哦。我现在明白他对“特定类别词典”的意思了。
  • 您可能希望将检查 if not 'my_config' in cls.__dict__ 移动到 for 循环之前,这样它就不会每次都执行。

标签: python class metaprogramming


【解决方案1】:

更正

def config(name, description, default=None):
    ComponentMeta.config_items.append((name, description, default))

class ComponentMeta(type):
    config_items = []
    def __init__(cls, clsname, bases, clsdict):
        for options in ComponentMeta.config_items:
                cls.add_config(*options)
        ComponentMeta.config_items = []

class Component(object):
    __metaclass__ = ComponentMeta
    config_items = [] # this is only for testing. you don't need it
    @classmethod
    def add_config(cls, name, description, default=None):
        #also for testing
        cls.config_items.append((name, description, default))

class Model1(Component):
    config('n', 'number of burz to instigate')
    config('skip', 'skip any barz in the data', default=True)

print Model1.config_items

这通过使 config 成为将项目添加到 ComponentMeta.config_instances 的外部函数来工作。 ComponentMeta 然后在创建类时检查此列表并在项目上调用 config_item。请注意,这不是线程安全的(尽管我可以这样做)。此外,如果ComponentMeta 的子类未能调用super 或为空ComponentMeta.config_items 本身,则下一个创建的类将获取配置项。

【讨论】:

  • 感谢您的回答。但它似乎不起作用。我想我理解哲学,但我不明白这些行的含义: ''' class Model1(Component): config('n', 'number of burz to instigate') config('skip', 'skip any barz in the data', default=True) ''' 其中两个配置对象被创建,对象如何放入类字典中?
  • @Anrdea。正确的。其实我有点尴尬请参阅我最近的更新,它为您提供了您想要的模数潜在问题。详情见答案。
  • 非常感谢!我发现我自己永远不会发现的技巧是将一些东西放在临时存储区域中,然后在每次定义类时重置它。
  • @Andrea 很高兴我能帮上忙。请注意,您可能希望将config 设为ComponentMeta 的类方法(可能将Component 重命名为ComponentBase,将其重命名为Component。这更清楚地表明它只能在上下文中使用创建一个子类。
【解决方案2】:

一旦执行主体,就会定义一个类名。导入包含该类的文件时会发生这种情况。这与创建类的实例不同。

class A(object):
    """ doc """
    config = []
    def __init__(def):
        pass
A.config.append(('n', 'number of burz to instigate'))
A.config.append(('skip', 'skip any barz in the data', default=True))
# access class specific config before object instantiation
x = A.config[0]
# instantiating your class, post configuration access
a_obj = A()

这种方法不符合您的目的吗?类配置可以存储在类变量中,可以在实例化该类的任何对象之前对其进行修改和添加。

使用类变量应该可以达到您的目的。

【讨论】:

  • 您不必调用 append:您可以在创建类时将 config 设为列表文字。
  • 是的,这是另一种解决方案。我想知道它是否可以变得不那么冗长。 (从美学角度来说,我更希望配置出现在类定义中)
【解决方案3】:

为什么不做一些简单的事情,比如:

def makeconfigdict(cls):
    thedict = {}
    for name, value in cls.__dict__.items():
       if isaconfig(value):
           addconfigtodict(thedict, name, value)
           delattr(cls, name)
    cls.specialdict = thedict
    return cls

@makeconfigdict
class Model1(Component):
    n = config('number of burz to instigate')
    skip = config('skip any barz in the data', default=True)
    ...

只要config 函数返回isaconfig 函数可以识别的对象,并且addconfigtodict 函数可以以您想要的任何格式正确设置到特殊字典中,那么您就处于三叶草中。

如果我正确理解您的规格,您不会想要像 Model1.skip 这样的普通属性,对吧?这就是为什么我在makeconfigdict 中有delattr 调用(以及为什么我在类的字典中使用.items() ——因为字典在循环期间被修改,最好使用.items(),它需要一个“快照" 所有名称和值的列表,而不是通常的 .iteritems(),它只是在字典上迭代,因此不允许在循环期间对其进行修改。

【讨论】:

  • 感谢亚历克斯的回答。那是另一种选择。是的,我也不想要一个普通的属性。我想我会使用提出的其他解决方案,它在其他方面是“hacky”,但它似乎对用户更友好(用户可能期望 Model1.skip 指的是某些东西)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-04
  • 1970-01-01
  • 2013-12-31
  • 1970-01-01
相关资源
最近更新 更多