【问题标题】:Most efficient way to add prefix to Python dictionary keys向 Python 字典键添加前缀的最有效方法
【发布时间】:2013-05-26 17:31:18
【问题描述】:

所以我发现自己需要在 Python 字典中添加前缀。

基本上我想要的是这个字典的用户能够在字典的实例化时添加一个前缀,在这种情况下,字典保存前缀并且每次添加新键时,它都会添加前缀。但是如果由于某种原因没有提供或更改前缀,我也想改变字典,这意味着旧的字典键需要在它们前面加上前缀,同时保持它们各自的值。

用例:

基本上我正在完成MWS API 的最后一个api。 我围绕每个调用都需要采用特定参数的想法构建了 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

不幸的是,最后两个 api 很难以这种方式实现,因为它们利用了亚马逊引入的一个名为 Datatypes 的新“功能”。

这些“Datatypes”是嵌套结构。 例如:

我想从InboundShipmentAPI 调用CreateInboundShipment 操作,

该操作采用以下参数:

ShipmentId - String
InboundShipmentHeader - InboundShipmentHeader datatype
InboundShipmentItems - A list of InboundShipmentItem datatypes

问题的发生是因为 InboundShipmentHeader 是一种将另一种数据类型作为参数的数据类型。 最后,亚马逊期望如下:

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',

所以我想让某人进行此调用变得简单,而不必担心每个参数的名称。 我的解决方案是创建一个基本数据类型类,然后创建单独的数据类型 作为类。

这是我目前所拥有的:

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',
    }

然后代替做

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)

我想传递类似的东西

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)

在后台,call_amazon 方法会:

ShipmentHeader.prefix = InboundShipmentHeader

【问题讨论】:

  • 在添加新前缀之前,您是否还需要删除旧前缀?
  • 在前缀的设置器中,我正在清除字典并将 _prefix 的值替换为新值。
  • 但是你的字典键会同时添加新旧前缀。
  • 只是出于好奇,您为什么需要这样做?
  • @interjay 我明白你的意思了 :(,我想现在可以忽略。

标签: python performance dictionary


【解决方案1】:

您可以继承 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

现在,界面有点 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'

header.dict() 的输出为:

{'InboundShipmentHeader.AreCasesRequired': 'somevalue',
 'InboundShipmentHeader.DestinationFulfillmentCenterId': 'somevalue',
 'InboundShipmentHeader.LabelPrepPreference': 'somevalue',
 'InboundShipmentHeader.ShipFromAddress.Name': 'Foo',
 'InboundShipmentHeader.ShipmentName': 'somevalue',
 'InboundShipmentHeader.ShipmentStatus': 'somevalue'}

【讨论】:

    【解决方案2】:

    从外观上看,您在抽象类中所需的翻译比仅在字典键前加前缀要复杂一些。

    我可能会将翻译逻辑封装在一个基类中,并为每种类型创建子类,就像这样......

    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())
    

    ...它还处理子“对象”的递归,并输出...

    {'InboundShipmentHeader.ShipFromAddress.AddressLine1': '123 High Street',
     'InboundShipmentHeader.ShipFromAddress.Name': 'Fred Bloggs',
     'InboundShipmentHeader.ShipmentName': 'Something'}
    

    ...假设这是亚马逊所期望的格式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-21
      • 1970-01-01
      相关资源
      最近更新 更多