2014-11-05 30 views
21

是否可以將OrderedDict實例傳遞給使用**kwargs語法並保留排序的函數?在** kwargs中使用OrderedDict

我希望做的是:

def I_crave_order(**kwargs): 
    for k, v in kwargs.items(): 
     print k, v 

example = OrderedDict([('first', 1), ('second', 2), ('third', -1)]) 

I_crave_order(**example) 
>> first 1 
>> second 2 
>> third -1 

但是實際結果是:

>> second 2 
>> third -1 
>> first 1 

即典型的隨機字典排序。

我有其他用途,其中明確設置的順序是很好的,所以我想保持**kwargs而不是僅僅通過OrderedDict作爲常規參數

+0

感謝您的詳細答覆。我最終做的是允許一個可選的第一個參數(一個有序的詞典),如果提供的話,將優先使用** kwargs。偉大的信息,雖然 – theodox 2014-11-05 16:30:18

+0

**另請參閱:** https://duckduckgo.com/?q=pep468 – dreftymac 2017-04-06 11:55:20

回答

22

對於Python 3.6,the keyword argument order is preserved的。在3.6之前,這是不可能的,因爲OrderedDict變成了dict


實現的第一件事是,你在**example傳遞值不會自動成爲**kwargs值。考慮這種情況下,如果kwargs只會有example部分:

def f(a, **kwargs): 
    pass 
example = {'a': 1, 'b': 2} 
f(**example) 

或者這種情況下,在那裏將有更多的價值比那些例子:

example = {'b': 2} 
f(a=1, c=3, **example) 

甚至沒有重疊可言:

example = {'a': 1} 
f(b=2, **example) 

所以,你問什麼,並沒有真正意義。

不過,如果有一些方法來指定要有序**kwargs,不管它可能是好的,其中的關鍵字從-明確的關鍵字ARGS中出現的順序來了,然後是所有的項目**example按它們在example中出現的順序排列(如果exampledict,則這可能是任意的,但如果它是OrderedDict,也可能是有意義的)。

定義所有的煩雜細節,並保持表現可接受,結果比聽起來更難。請參閱PEP 468以及鏈接的線索,瞭解關於該想法的一些討論。這段時間似乎停滯不前,但是如果有人拿起並且支持它(並且寫一個參考實現供人們使用 - 這取決於從C API可訪問的OrderedDict,但是希望在3.5+ ),我懷疑它最終會融入語言。


順便說一句,注意,如果這個可能,這將幾乎肯定會在構造函數用於OrderedDict本身。但是,如果你嘗試,你正在做的是凍結一些任意順序作爲永久順序:

>>> d = OrderedDict(a=1, b=2, c=3) 
OrderedDict([('a', 1), ('c', 3), ('b', 2)]) 

同時,你有什麼選擇?

那麼,顯而易見的選擇就是通過example作爲一個正常的參數,而不是拆包:

def f(example): 
    pass 
example = OrderedDict([('a', 1), ('b', 2)]) 
f(example) 

或者,當然,你可以使用*args並將項目傳遞元組,但是這通常醜陋。

從PEP鏈接的線程中可能會有一些其他的解決方法,但實際上,它們都不會比這更好。 (除了...... IIRC,李浩義提出了一個基於他的MacroPy的解決方案來傳遞保留順序的關鍵字參數,但我不記得細節。如果你願意使用MacroPy並編寫代碼,MacroPy的解決方案通常很棒它不完全像Python一樣閱讀,但並不總是合適的......)

+0

你能更新python 3.6的這個答案嗎? – Moberg 2017-02-23 12:04:16

2

當Python在簽名中遇到**kwargs構造時,它預計kwargs是一個「映射」,這意味着兩件事:(1)能夠調用kwargs.keys()來獲得映射所包含的鍵的迭代,以及(2)可以針對由keys()返回的迭代器中的每個鍵調用kwargs.__getitem__(key),並且所得到的值是所要關聯的那個值鍵。

內部,Python會再「改造」無論映射成一個字典,有點像這樣:

**kwargs -> {key:kwargs[key] for key in kwargs.keys()} 

這看起來有點傻,如果你認爲kwargs已經是一個dict - 它會,因爲沒有理由從傳入的一個構造完全等價的dict

但當kwargs不一定是dict,那麼它很重要,以使其內容分解成一個合適的默認數據結構,該代碼執行拆包的參數總是知道它的工作原理。

所以,你可以亂用一個特定的數據類型被散裝的,但由於轉化爲dict的一致ARG-拆包協議着想的方式,它只是碰巧把擔保的順序參數拆包是不可能的(因爲dict沒有跟蹤添加元素的順序)。如果Python的語言**kwargs代入OrderedDict而不是dict(意味着密鑰的命令作爲關鍵字參數將是它們遍歷的順序),那麼通過傳遞OrderedDict或其他keys()方面的其他數據結構某種排序,你可以可能期望一定的順序上的參數。這只是一個奇怪的實現,dict被選爲標準,而不是其他類型的映射。

下面是可以「解壓」,而是一類總是將所有解壓後的數值爲42(即使他們是不是真的)的一個愚蠢的例子:

class MyOrderedDict(object): 
    def __init__(self, odict): 
     self._odict = odict 

    def __repr__(self): 
     return self._odict.__repr__() 

    def __getitem__(self, item): 
     return 42 

    def __setitem__(self, item, value): 
     self._odict[item] = value 

    def keys(self): 
     return self._odict.keys() 

然後定義一個函數來打印解壓內容:

def foo(**kwargs): 
    for k, v in kwargs.iteritems(): 
     print k, v 

,並作出價值,並嘗試一下:

In [257]: import collections; od = collections.OrderedDict() 

In [258]: od['a'] = 1; od['b'] = 2; od['c'] = 3; 

In [259]: md = MyOrderedDict(od) 

In [260]: print md 
OrderedDict([('a', 1), ('b', 2), ('c', 3)]) 

In [261]: md.keys() 
Out[261]: ['a', 'b', 'c'] 

In [262]: foo(**md) 
a 42 
c 42 
b 42 

這款C對鍵值對的傳遞(在這裏,總是返回42)是你在Python中如何工作的能力的程度。

修改*args得到解包的方式稍微有點靈活。對於更多的看到這個問題:< Does argument unpacking use iteration or item-getting?>。

13

This is now the default in python 3.6

Python 3.6.0a4+ (default:d43f819caea7, Sep 8 2016, 13:05:34) 
>>> def func(**kw): print(kw.keys()) 
... 
>>> func(a=1, b=2, c=3, d=4, e=5) 
dict_keys(['a', 'b', 'c', 'd', 'e']) # expected order 

這是不可能的,如其他答案所述。