Я хочу иметь возможность динамически генерировать атрибуты класса из списка или словаря. Идея состоит в том, что я могу определить список атрибутов, а затем иметь доступ к этим атрибутам, используя 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_type
для campaign
. Это, очевидно, немного сложно, поскольку 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]
Это позволяет мне легко получить доступ к значению метки, и если метка относится к типу, который мне интересен, он вернет соответствующее значение, иначе будет возвращено 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]
, а затем есть некоторый l oop, который создает атрибуты.
В свою очередь, я могу применить аналогичный подход к моему другие классы, так что, хотя объект Campaign
может содержать объект Label
, я могу получить доступ к значению метки просто по my_campaign.campaign_type
. Если в кампании нет метки соответствующего типа, она просто вернет False
.