2

我希望能够从列表或字典中动态生成类的属性。这个想法是我可以定义一个属性列表,然后能够使用访问这些属性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)

返回 的campaign_typecampaign。这显然有点复杂,因为campaign_type实际上是 a并且做了一些工作来从基础对象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]

这使我可以轻松访问标签的值,如果标签是我关心的类型,它将返回相关值,否则将返回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 标签对象,它看起来像这样:

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

然后my_label.campaign_type会返回Primary,同样my_label.match_type会返回False


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

    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]然后有一些循环来创建属性。

反过来,我可以将类似的方法级联到我的其他类,这样虽然一个Campaign对象可能包含一个Label对象,但我可以通过简单地访问标签的值my_campaign.campaign_type。如果活动没有适当类型的标签,它将简单地返回False

4

2 回答 2

1

虽然cls在类体运行时不存在,但您可以通过简单地locals()在类体内部返回的字典中设置 then 来设置属性:

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 


除此之外,您可以使用元类,是的,但也可以使用__init_suclass__基类。更少的元类意味着更少的“移动部件”可以在你的系统中以奇怪的方式表现。

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

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

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

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

于 2020-07-09T12:57:28.263 回答
0

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

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()
于 2020-07-09T09:07:17.623 回答