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_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]
这使我能够轻松访问标签的值,如果标签是我关心的类型,它将 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__
本身中存储与描述符相关的数据更为常见,而不是在描述符本身中创建 data
和 cached_data
- 所以一个人不需要关心 weakrefs - 但两种方法都有效(就在本周,我已经以这种方式实现了一个描述符,尽管我通常会选择实例的 __dict__
)
我希望能够从列表或字典中动态生成 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_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]
这使我能够轻松访问标签的值,如果标签是我关心的类型,它将 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__
本身中存储与描述符相关的数据更为常见,而不是在描述符本身中创建 data
和 cached_data
- 所以一个人不需要关心 weakrefs - 但两种方法都有效(就在本周,我已经以这种方式实现了一个描述符,尽管我通常会选择实例的 __dict__
)