Python: 从列表中动态生成属性

Python: Dynamically generating attributes from a list

我希望能够从列表或字典中动态生成 class 的属性。这个想法是我可以定义一个属性列表,然后能够使用 my_class.my_attribute

访问这些属性

例如:

class Campaign(metaclass=MetaCampaign):
    _LABELS = ['campaign_type', 'match_type', 'audience_type'] # <-- my list of attributes
    
    for label in _LABELS:
        setattr(cls, label, LabelDescriptor(label))
    
    def __init__(self, campaign_protobuf, labels)
        self._proto = campaign_protobuf
        self._init_labels(labels_dict)
        
    def _init_labels(self, labels_dict):
        # magic...

这显然行不通,因为 cls 不存在,但我想:

my_campaign = Campaign(campaign, label_dict)
print(my_campaign.campaign_type)

到return的值campaign_typecampaign。这显然有点复杂,因为 campaign_type 实际上是一个 Descriptor 并且做了一些工作来从基础 Label 对象中检索值。


描述符:

class DescriptorProperty(object):
    def __init__(self):
        self.data = WeakKeyDictionary()

    def __set__(self, instance, value):
        self.data[instance] = value


class LabelTypeDescriptor(DescriptorProperty):
    """A descriptor that returns the relevant metadata from the label"""
    def __init__(self, pattern):
        super(MetaTypeLabel, self).__init__()
        self.cached_data = WeakKeyDictionary()
        # Regex pattern to look in the label:
        #       r'label_type:ThingToReturn'
        self.pattern = f"{pattern}:(.*)"

    def __get__(self, instance, owner, refresh=False):
        # In order to balance computational speed with memory usage, we cache label values
        # when they are first accessed.        
        if self.cached_data.get(instance, None) is None or refresh:
            ctype = re.search(self.pattern, self.data[instance].name) # <-- does a regex search on the label name (e.g. campaign_type:Primary)
            if ctype is None:
                ctype = False
            else:
                ctype = ctype.group(1)
            self.cached_data[instance] = ctype
        return self.cached_data[instance]

这使我能够轻松访问标签的值,如果标签是我关心的类型,它将 return 相关值,否则它将 return False.


标签对象:

class Label(Proto):
    _FIELDS = ['id', 'name']
    _PROTO_NAME = 'label'
    #  We define what labels can pull metadata directly through a property
    campaign_type = LabelTypeDescriptor('campaign_type')
    match_type = LabelTypeDescriptor('match_type')
    audience_type = LabelTypeDescriptor('audience_type')

    def __init__(self, proto, **kwargs):
        self._proto = proto
        self._set_default_property_values(self)  # <-- the 'self' is intentional here, in the campaign object a label would be passed instead.

    def _set_default_property_values(self, proto_wrapper):
        props = [key for (key, obj) in self.__class__.__dict__.items() if isinstance(obj, DescriptorProperty)]
        for prop in props:
            setattr(self, prop, proto_wrapper)

所以如果我有一个 protobuf 标签对象存储在我的 Label 中(基本上只是一个包装器),它看起来像这样:

resource_name: "customers/12345/labels/67890"
id {
  value: 67890
}
name {
  value: "campaign_type:Primary"
}

那么my_label.campaign_type会returnPrimary,同样my_label.match_type会returnFalse


原因是我正在创建许多 classes,它们都以相同的方式标记,并且可能有很多标签。目前这一切都按照描述工作,但我希望能够更动态地定义属性,因为它们基本上都遵循相同类型的模式。所以不是:

    campaign_type = LabelTypeDescriptor('campaign_type')
    match_type = LabelTypeDescriptor('match_type')
    audience_type = LabelTypeDescriptor('audience_type')
    ... # (many more labels)

我只是有: _LABELS = ['campaign_type', 'match_type', 'audience_type', ... many more labels] 然后有一些创建属性的循环。

反过来,我可以将类似的方法级联到我的其他 classes,这样虽然 Campaign 对象可能包含 Label 对象,但我可以访问标签只需 my_campaign.campaign_type。如果广告系列没有适当类型的标签,它只会 return False.

您可以定义一个 classmethod 来初始化这些属性,并在 class 声明之后调用此方法:

class Campaign(metaclass=MetaCampaign):
    _LABELS = ['campaign_type', 'match_type', 'audience_type'] # <-- my list of attributes
    
    @classmethod
    def _init_class(cls):
       for label in cls._LABELS:
        setattr(cls, label, LabelDescriptor(label))
# After the class has been declared, initialize the attributes
Campaign._init_class()

而class主体为运行时cls不存在,可以在locals()返回的字典中设置then来设置属性class正文:

class Campaign(metaclass=MetaCampaign):
    _LABELS = ['campaign_type', 'match_type', 'audience_type'] # <-- my list of attributes
    
    for label in _LABELS:
        locals()[label] = label, LabelDescriptor(label)
    del label  # so you don't get a spurious "label" attribute in your class 


除此之外,您还可以使用 metaclass,是的,但也可以在基础 class 上使用 __init_suclass__。更少的元classes 意味着更少的“移动部件”,这些部件可能在您的系统中以奇怪的方式运行。

因此,假设您的 Proto class 是所有其他需要此功能的人的基础:

class Proto:
    def __init_subclass__(cls, **kwd):
        super().__init_subclass__(**kwd)
        for label in cls._LABELS:
            setattr(cls, label, LabelDescriptor(label))
    ...

我已经查看了您的描述符和那里的代码 - 如果它们已经在工作,我会说它们没问题。

我可以评论说,在实例的 __dict__ 本身中存储与描述符相关的数据更为常见,而不是在描述符本身中创建 datacached_data - 所以一个人不需要关心 weakrefs - 但两种方法都有效(就在本周,我已经以这种方式实现了一个描述符,尽管我通常会选择实例的 __dict__