[英]Most efficient way to add prefix to Python dictionary keys
So I find myself needing to add a prefix to a Python dictionary. 所以我发现自己需要在Python字典中添加前缀。
Basically what I want is for the user of this dictionary to be able to add a prefix on instantiation of the dictionary, in which case the dictionary holds the prefix and everytime a new key is added, it prepends the prefix. 基本上我想要的是这个字典的用户能够在字典的实例化中添加前缀,在这种情况下字典保存前缀,并且每次添加新密钥时,它都会预先添加前缀。 But I also want mutate the dictionary if for some reason the prefix is not provided or changed, this means that the old dictionary keys need to have the prefix prepended to them while keeping their respective values. 但是如果由于某种原因没有提供或改变前缀,我也想要改变字典,这意味着旧的字典键需要在保留各自值的同时为它们添加前缀。
Use case: 使用案例:
Basically I'm finishing up the last apis of the MWS API . 基本上我正在完成MWS API的最后一次使用 。 I built the api around the idea that every call needed to take specific parameters, like : 我建立了api,认为每次调用都需要采取特定的参数,例如:
def get_report(self, marketplaceids):
# Here I process marketplaceids which is a python list
# and send the following to Amazon:
MarketplaceIdList.Id.1: 123,
MarketplaceIdList.Id.2: 345,
MarketplaceIdList.Id.3: 4343
# By doing this I eliminate the complexity of the arguments Amazon expects
Unfortunately the last two apis are harder to implement this way because they make use of a new "feature" Amazon introduced called Datatypes
. 不幸的是,最后两个api更难以这种方式实现,因为它们利用亚马逊引入的一个名为Datatypes
的新“特性”。
These " Datatypes
" are nested structures. 这些“ Datatypes
”是嵌套结构。 For example: 例如:
I want to call the CreateInboundShipment
action from the InboundShipmentAPI
, 我想从InboundShipmentAPI
调用CreateInboundShipment
操作,
The action takes the following arguments: 该操作采用以下参数:
ShipmentId - String
InboundShipmentHeader - InboundShipmentHeader datatype
InboundShipmentItems - A list of InboundShipmentItem datatypes
the problem happens because InboundShipmentHeader is a datatype that takes another datatype as argument. 问题发生的原因是InboundShipmentHeader是一种数据类型,它采用另一种数据类型作为参数。 In the end Amazon expects the following: 亚马逊最终期望如下:
ShipmentId=102038383
InboundShipmentHeader.ShipmentName': 'somevalue',
InboundShipmentHeader.ShipFromAddress.Name': 'somevalue',
InboundShipmentHeader.ShipFromAddress.AddressLine1': 'somevalue',
InboundShipmentHeader.ShipFromAddress.City': 'somevalue',
InboundShipmentHeader.ShipFromAddress.StateOrProvinceCode': 'somevalue',
InboundShipmentHeader.ShipFromAddress.PostalCode': 'somevalue',
InboundShipmentHeader.ShipFromAddress.CountryCode': 'somevalue',
InboundShipmentHeader.DestinationFulfillmentCenterId': 'somevalue',
InboundShipmentHeader.ShipmentStatus': 'somevalue',
InboundShipmentHeader.LabelPrepPreference': 'somevalue',
InboundShipmentItems.member.1.QuantityShipped': 'somevalue',
InboundShipmentItems.member.2.QuantityShipped': 'somevalue',
InboundShipmentItems.member.1.SellerSKU': 'somevalue',
InboundShipmentItems.member.2.SellerSKU': 'somevalue',
InboundShipmentHeader.ShipFromAddress.AddressLine2': 'somevalue',
InboundShipmentHeader.ShipFromAddress.DistrictOrCounty': 'somevalue',
so I want to make it simple for someone to make this call without having to worry about the names of each argument. 所以我想简单地让某人做这个电话而不必担心每个参数的名字。 My solution is to create a base datatype class and then create the separate datatypes as classes. 我的解决方案是创建一个基本数据类型类,然后创建单独的数据类型作为类。
This is what I have so far: 这是我到目前为止:
class AmazonDataType(dict):
"""
Base for all Amazon datatypes.
"""
def __init__(self, *args, **kwargs):
self._prefix = kwargs.pop('prefix', '')
self.update(*args, **kwargs)
@property
def prefix(self):
return self._prefix
@prefix.setter
def prefix(self, value):
self._prefix = value
newdict = {'%s.%s' % (value, key): dictvalue for key, dictvalue in self.iteritems()}
self.clear()
dict.update(self, newdict)
def __setitem__(self, key, value):
try:
original_key = self.fields[key]
except KeyError, e:
raise e
if isinstance(value, AmazonDataType):
value.prefix = original_key
dict.update(self, value)
else:
newkey = self.prefix + original_key if self.prefix else original_key
dict.__setitem__(self, newkey, value)
def update(self, *args, **kwargs):
"""
Props to Matt Anderson (http://stackoverflow.com/a/2390997/389453)
"""
for k, v in dict(*args, **kwargs).iteritems():
self[k] = v
class InboundShipmentHeader(AmazonDataType):
fields = {
'name': 'ShipmentName',
'address': 'ShipFromAddress',
'fulfillment_center_id': 'DestinationFulfillmentCenterId',
'label_preference': 'LabelPrepPreference',
'cases_required': 'AreCasesRequired',
'shipment_status': 'ShipmentStatus',
}
then instead of doing 然后而不是做
somedict = {
'InboundShipmentHeader.ShipmentName': 'somevalue',
'InboundShipmentHeader.ShipFromAddress.Name': 'somevalue',
'InboundShipmentHeader.ShipFromAddress.AddressLine1': 'somevalue',
'InboundShipmentHeader.ShipFromAddress.City': 'somevalue',
'InboundShipmentHeader.ShipFromAddress.StateOrProvinceCode': 'somevalue',
'InboundShipmentHeader.ShipFromAddress.PostalCode': 'somevalue',
'InboundShipmentHeader.ShipFromAddress.CountryCode': 'somevalue',
'InboundShipmentHeader.DestinationFulfillmentCenterId': 'somevalue',
'InboundShipmentHeader.ShipmentStatus': 'somevalue',
'InboundShipmentHeader.LabelPrepPreference': 'somevalue',
}
call_amazon(somedict)
I want to pass something like 我想通过类似的东西
ShipmentHeader = InboundShipmentHeader()
ShipmentHeader['name'] = 'somevalue'
ShipmentHeader['address'] = address_datatype_instance
ShipmentHeader['fulfillment_center_id'] = 'somevalue'
ShipmentHeader['label_preference'] = 'somevalue'
ShipmentHeader['cases_required'] = 'somevalue'
ShipmentHeader['shipment_status'] = 'somevalue'
call_amazon(ShipmentHeader, otherparams)
In the background, the call_amazon
method does: 在后台, call_amazon
方法执行:
ShipmentHeader.prefix = InboundShipmentHeader
You could subclass dict
and add a method (I'm not sure what to call it, so let's say dict
): 你可以继承dict
并添加一个方法(我不知道该怎么称呼它,所以让我们说dict
):
class AmazonDataType(dict):
"""
Base for all Amazon datatypes.
"""
def __init__(self, *args, **kwargs):
self._prefix = kwargs.pop('prefix', self.__class__.__name__)
super(AmazonDataType, self).__init__(*args, **kwargs)
def __getattr__(self, key):
return self.__getitem__(key)
def __setattr__(self, key, value):
return self.__setitem__(key, value)
def dict(self):
result = {}
for key, value in self.items():
if key.startswith('_'):
continue
key = self.fields.get(key, key)
if isinstance(value, AmazonDataType):
for skey, svalue in value.dict().items():
result['%s.%s' % (self._prefix, skey)] = svalue
else:
result['%s.%s' % (self._prefix, key)] = value
return result
Now, the interface is a little more Pythonic: 现在,界面更加Pythonic:
class InboundShipmentHeader(AmazonDataType):
fields = {
'name': 'ShipmentName',
'address': 'ShipFromAddress',
'fulfillment_center_id': 'DestinationFulfillmentCenterId',
'label_preference': 'LabelPrepPreference',
'cases_required': 'AreCasesRequired',
'shipment_status': 'ShipmentStatus',
}
class Address(AmazonDataType):
fields = {
'name': 'Name',
'address': 'AddressLine1',
'city': 'City'
}
address = Address(prefix='ShipFromAddress')
address.name = 'Foo'
header = InboundShipmentHeader()
header.name = 'somevalue'
header.address = address
header.fulfillment_center_id = 'somevalue'
header.label_preference = 'somevalue'
header.cases_required = 'somevalue'
header.shipment_status = 'somevalue'
The output of header.dict()
is: header.dict()
的输出是:
{'InboundShipmentHeader.AreCasesRequired': 'somevalue',
'InboundShipmentHeader.DestinationFulfillmentCenterId': 'somevalue',
'InboundShipmentHeader.LabelPrepPreference': 'somevalue',
'InboundShipmentHeader.ShipFromAddress.Name': 'Foo',
'InboundShipmentHeader.ShipmentName': 'somevalue',
'InboundShipmentHeader.ShipmentStatus': 'somevalue'}
By the looks of it, the translation you require in your abstraction class is a little more complicated that just prefixing dictionary keys. 从它的外观来看,你在抽象类中需要的翻译稍微复杂一点,就是为字典键添加前缀。
I'd probably encapsulate the translation logic in a base class, and create subclasses for each type, with something like this... 我可能会将翻译逻辑封装在基类中,并为每种类型创建子类,使用类似的东西......
class AmazonDict(dict):
translation_dict = {}
def __init__(self, prefix):
self.prefix = prefix
def translate(self):
result = {}
for k, v in self.iteritems():
if k not in self.translation_dict:
continue
if isinstance(v, AmazonDict):
for sk, sv in v.translate().iteritems():
sk = '%s.%s' % (self.prefix, sk)
result[sk] = sv
else:
k = '%s.%s' % (self.prefix, self.translation_dict[k])
result[k] = v
return result
class ShipmentAddress(AmazonDict):
translation_dict = {'name': 'Name',
'line1': 'AddressLine1'}
class ShipmentHeader(AmazonDict):
translation_dict = {'name': 'ShipmentName',
'address': 'ShipFromAddress'}
address = ShipmentAddress('ShipFromAddress')
address['name'] = 'Fred Bloggs'
address['line1'] = '123 High Street'
header = ShipmentHeader('InboundShipmentHeader')
header['name'] = 'Something'
header['address'] = address
pprint.pprint(header.translate())
...which also handles the recursion for child 'objects', and outputs... ...还处理子'对象'的递归,并输出......
{'InboundShipmentHeader.ShipFromAddress.AddressLine1': '123 High Street',
'InboundShipmentHeader.ShipFromAddress.Name': 'Fred Bloggs',
'InboundShipmentHeader.ShipmentName': 'Something'}
...assuming that's the format which Amazon is expecting. ...假设这是亚马逊期待的格式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.