值设置为列表的Python字典表现奇怪

时间:2015-07-16 09:10:52

标签: python list dictionary

我知道list对象可以就地更改,但list()每次调用它时都会返回一个独立的list对象。现在我对输出感到困惑:

L = [1, 2, 3, 4]
# list() should 
D = dict.fromkeys(L, list())
print(D) # {1: [], 2: [], 3: [], 4: []}
D[1].append(1989)
print(D) # {1: [1989], 2: [1989], 3: [1989], 4: [1989]}

所需的输出应为:

{1: [1989], 2: [], 3: [], 4: []}

4 个答案:

答案 0 :(得分:4)

代码的问题是fromkeys构造函数为每个键重用相同的值。因此,list()仅评估一次。

你可以使用dict-comprehension:

D = {x:list() for x in L}

或者,如果您知道您将始终使用字典中的列表,请使用defaultdict工厂:

from collections import defaultdict

D = defaultdict(list)
D[1].append(1989)
D[2].append(2001)

print(D) # {1: [1989], 2: [2001]}

答案 1 :(得分:2)

public static class Program { [STAThread] public static void Main(params String[] arguments) { var bindingList = new BindingList<Record>(); Action<Object> action = (argument) => { var synchronizationContext = argument as SynchronizationContext; var dispatcher = argument as Dispatcher; if ((synchronizationContext == null) && (dispatcher == null)) { throw new ArgumentException(@"argument"); } else { var random = new Random(); while (true) { Action<Object> sendOrPostCallbackAction = (state) => { var record = new Record(); bindingList.Add(record); }; var sendOrPostCallback = new SendOrPostCallback(sendOrPostCallbackAction); // WindowsFormsSynchronizationContext.Current.Send(sendOrPostCallback, new Object()); // *** NullReferenceException // *** Object reference not set to an instance of an object... // *** Thought this could be found not matter where. // *** Cause the winforms application is already started at that point in the code. // Dispatcher.CurrentDispatcher.Invoke(sendOrPostCallback, (Object)null); // *** InvalidOperationException // *** Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on. if (dispatcher != null) { // Only Works if the synchronizationContext is the one used by the UI Thread... dispatcher.Invoke(sendOrPostCallback, (Object)null); } else { // Only Works if the synchronizationContext is the one used by the UI Thread... synchronizationContext.Send(sendOrPostCallback, (Object)null); } Thread.Sleep(100); } } }; Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var mainForm = new MainForm { DataGridView = { DataSource = bindingList }, }; mainForm.ButtonPassSynchronizationContext.Click += (sender, args) => { mainForm.ButtonPassSynchronizationContext.Enabled = false; mainForm.ButtonPassDispatcher.Enabled = false; // Of course here the synchronization context is the one used by the UI Thread... // WindowsFormsSynchronizationContext.Current should be equal here. var areTheSameSyncrhonizationContexts = SynchronizationContext.Current == WindowsFormsSynchronizationContext.Current; Debug.WriteLineIf(areTheSameSyncrhonizationContexts, @"The sychronization contexts are the same."); Task.Factory.StartNew(action, SynchronizationContext.Current); }; mainForm.ButtonPassDispatcher.Click += (sender, args) => { mainForm.ButtonPassSynchronizationContext.Enabled = false; mainForm.ButtonPassDispatcher.Enabled = false; // The dispatcher is indeed not null, since it is the one used by the UI Thread... // Dispatcher.CurrentDispatcher should be not null here. var isDispatcherNotNull = Dispatcher.CurrentDispatcher != null; Debug.WriteLineIf(isDispatcherNotNull, @"The Current Dispatcher is not null."); Task.Factory.StartNew(action, Dispatcher.CurrentDispatcher); }; Application.Run(mainForm); } public class Record { public Record() { var random = new Random(); this._dummyProperty = random.Next(0, 100); } private readonly Int32 _dummyProperty; public Int32 DummyProperty { get { return this._dummyProperty; } } } public class MainForm : Form { public MainForm() { this.InitializeComponent(); } public Button ButtonPassSynchronizationContext { get; private set; } public Button ButtonPassDispatcher { get; private set; } public DataGridView DataGridView { get; private set; } private void InitializeComponent() { this.Text = @"Main Form"; this.StartPosition = FormStartPosition.CenterScreen; this.DataGridView = new DataGridView { Dock = DockStyle.Fill, AllowUserToAddRows = false, AllowUserToDeleteRows = false, DefaultCellStyle = { Alignment = DataGridViewContentAlignment.MiddleCenter }, AlternatingRowsDefaultCellStyle = { BackColor = Color.LightGoldenrodYellow }, }; this.ButtonPassSynchronizationContext = new Button() { Dock = DockStyle.Bottom, Text = @"Pass SynchronizationContext", }; this.ButtonPassDispatcher = new Button() { Dock = DockStyle.Bottom, Text = @"Pass Dispatcher", }; this.Controls.Add(this.DataGridView); this.Controls.Add(this.ButtonPassSynchronizationContext); this.Controls.Add(this.ButtonPassDispatcher); } } } 被调用一次,同一list()被用作所有键的值。

控制台记录(对于所有值,您可以看到相同的list):

id

答案 2 :(得分:2)

您可以考虑使用defaultdict中的collections,它可能更合适,至少值得了解。

import collections
D = collections.defaultdict(list)

D[1].append(1989)
D[1].append(1990)

print D[1]
print D
print D[2]
print D

这将打印以下内容:

[1989, 1990]
defaultdict(<type 'list'>, {1: [1989, 1990]})
[]
defaultdict(<type 'list'>, {1: [1989, 1990], 2: []})

答案 3 :(得分:1)

list()只被调用一次。对于旧版本的Python,您可以使用以下事实:您可以从键/值对列表初始化dict:

D = dict([(x, list()) for x in L]

从Python 2.5开始,您可以使用生成器并省略上一个示例中的列表构造:

D = dict((x, list()) for x in L)

从python 2.7和3.0开始,您可以使用最初被拒绝的python 2.3,来自PEP 274的dict理解:

D = {x: list() for x in L}