質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.40%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

550閲覧

ディクショナリの1つの要素を操作すると他の要素まで同時に操作されてしまう。

union_15

総合スコア1

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2023/03/11 07:18

質問投稿の直前に自己解決したが、後から同じ問題に直面した人のために投稿し、解決方法を記す。

実現したいこと

  • あるカードゲームのデッキをtxtファイルから読み込みたい。
  • カードデータをディクショナリの形に変換し、ゲーム内で個別に参照・操作できるようにしたい。

前提

Pythonでカードゲームのプログラムを作っており、ゲームに用いるデッキをtxtファイルから読み込む機能を実装しようとしています。

ファイルパスの指定およびメッセージの表示にtkinterのfiledialogモジュールを使用しています。

カード情報には色とカード名があり、txtファイルではそれらのカード情報、およびカード枚数を指定しています。

発生した問題

該当コードの114行目の操作で問題が起こりました。

デッキMAIN_deckに格納される情報は以下のようになっています。

Python

1MAIN_deck = [{"name":"カード名", "color":, "tap":カードの状態(T/F), "select":選択中か否か(T/F), "position":カードの座標}, 2 {"name":"カード名", "color":, "tap":カードの状態(T/F), "select":選択中か否か(T/F), "position":カードの座標}, 3 ...]

例えば、MAIN_deckの0番目から3番目が同じ名前のカードのとき、0番目のカードに対して

Python

1MAIN_deck[0]['position'] = [100,0]

のように操作をした場合、MAIN_deck[0]からMAIN_deck[3]までのすべてについて、キー'position'の値が[100,0]に変更されてしまいます。

MAIN_deck[0]以外のカードの情報を変えることなくMAIN_deck[0]の情報を操作できるのが理想です。

該当のソースコード

Python

1from tkinter import filedialog 2from tkinter import messagebox 3 4# 色の設定 5# 原色 6red = (255, 0, 0) 7green = (0, 255, 0) 8blue = (0, 0, 255) 9yellow = (255, 255, 0) 10purple = (255, 0, 255) 11black = (0, 0, 0) 12white = (255, 255, 255) 13# 各文明 14light = (255, 255, 120) 15water = (120, 120, 255) 16darkness = (120, 120, 120) 17fire = (255, 120, 120) 18nature = (120, 255, 120) 19zero = white 20 21# txtファイル内の文字と色を対応させる関数 22def color_read(color): 23 if color in ["白", "光"]: 24 return light 25 elif color in ["青", "水"]: 26 return water 27 elif color in ["黒", "闇"]: 28 return darkness 29 elif color in ["赤", "火"]: 30 return fire 31 elif color in ["緑", "自然"]: 32 return nature 33 elif color in ["無", "零", "ゼロ"]: 34 return white 35 else: 36 return purple 37 38# txtファイルからデッキを読み取る関数 39def deck_read(): 40 # ファイルパスを格納する変数 41 # tkinter の filedialog によってブラウズウィンドウを表示、ファイル名を参照 42 file = filedialog.askopenfilename() 43 44 45 # ファイルが指定されなかったら空のデッキを返す 46 if file == "": 47 return [] 48 # txt ファイルでなければエラーメッセージを表示して空のデッキを返す 49 elif file[-3:] != "txt": 50 messagebox.showerror("Duel Masters", "txtファイルを選択してください") 51 return [] 52 53 # txt ファイルなら開く 54 else: 55 f = open(file,"r",encoding="utf-8") 56 57 # カードを入れるリスト 58 MAIN_deck = [] 59 60 61 # 1行ずつ処理 62 for line in f: 63 # 全角空白を半角空白に置換 64 line = line.replace("\n","") 65 66 # その行が空でなければ処理 67 if line != "": 68 # 最初の文字がカードの文明なら処理 69 if line[:1] in ("白", "青", "黒", "赤", "緑", "無", "光", "水", "闇", "火", "自", "零", "ゼ") and line[:2] != "零龍": 70 # その行を半角空白で分割、カード情報のリストにする 71 card_info_list = line.replace("\u3000"," ").split(" ") 72 # カード名を入れる変数 73 card_name = "" 74 # カードの枚数を入れる変数 75 card_value = 0 76 # カードの枚数が入力されていなかったら処理 77 if card_info_list[-1].isdigit() == False: 78 # カード情報が文明のみになるまで名前を結合 79 # カード名に空白が含まれている場合の処理 80 while len(card_info_list)>=2: 81 card_name += card_info_list[1] 82 card_info_list.pop(1) 83 # カード枚数を1枚に指定 84 card_value = 1 85 # カードデータの完成 86 card_info = {"name":card_name, "color":color_read(card_info_list[0]), "tap":False, "select":False, "position":[], "reverse":False} 87 else: 88 # カード情報が文明のみになるまで名前を結合 89 # カード名に空白が含まれている場合の処理 90 while len(card_info_list)>=3: 91 card_name += card_info_list[1] 92 card_info_list.pop(1) 93 # カード枚数を入力通りに指定 94 card_value = int(card_info_list[-1]) 95 # カードデータの完成 96 card_info = {"name":card_name, "color":color_read(card_info_list[0]), "tap":False, "select":False, "position":[], "reverse":False} 97 98 for i in range(card_value): 99 MAIN_deck.append(card_info) 100 101 # ファイルを閉じる 102 f.close() 103 104 #登録完了のメッセージ 105 messagebox.showinfo("Notice","デッキを登録しました") 106 # 読み取ったデッキを返す 107 return MAIN_deck 108 109# メイン部分 110# デッキを読み込み 111deck = deck_read() 112# デッキ内のカードそれぞれに番号を割り当てる 113for i in range(len(deck)): 114 deck[i]["card_num"] = i # ここがうまくいかない 115# 読み取ったデッキを表示 116print(deck)

テキストファイルのサンプル

  • 色 カード名 枚数 の順に記述
  • 全角スペースと半角スペースは区別しない

txt

1青黒緑 カード1 4 2緑 カード2 4 3緑 カード3 3 4青赤緑 カード4 4 5白 カード5 4 6白黒赤 カード6 2 7白青赤 カード7 4 8白黒赤 カード8 4 9黒 カード9 4 10赤緑 カード10 1 11白青 カード11 1 12青黒緑 カード12 1 13黒 カード13 1 14青 カード14 1 15緑 カード15 2

想定していた実行結果

キー'card_num'の値は0から39、1枚ごとに異なる

[{'name': 'カード1', 'color': (255, 0, 255), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 0}, {'name': 'カード1', 'color': (255, 0, 255), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 1}, {'name': 'カード1', 'color': (255, 0, 255), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 2}, {'name': 'カード1', 'color': (255, 0, 255), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 3}, {'name': 'カード2', 'color': (120, 255, 120), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 4}, ...]

実際の実行結果

キー'card_num'の値が同名カードで同じ値になっている

[{'name': 'カード1', 'color': (255, 0, 255), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 3}, {'name': 'カード1', 'color': (255, 0, 255), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 3}, {'name': 'カード1', 'color': (255, 0, 255), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 3}, {'name': 'カード1', 'color': (255, 0, 255), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 3}, {'name': 'カード2', 'color': (120, 255, 120), 'tap': False, 'select': False, 'position': [], 'reverse': False, 'card_num': 7}, ...]

試したこと

  • より単純な以下のコードdict_test.pyで実験。正しく処理され、想定通りの結果となった。

Python

1# dict_test.py 2dic_list = [] 3 4for i in range(4): 5 dic_list.append({"name":"a", "grade":1, "student":True}) 6 7for i in range(3): 8 dic_list.append({"name":"b", "grade":1, "student":True}) 9 10for i in range(2): 11 dic_list.append({"name":"c", "grade":3, "student":True}) 12 13for i in range(len(dic_list)): 14 dic_list[i]["num"] = i 15 16print(dic_list)

Python

1# 実行結果 2[{'name': 'a', 'grade': 1, 'student': True, 'num': 0}, {'name': 'a', 'grade': 1, 'student': True, 'num': 1}, {'name': 'a', 'grade': 1, 'student': True, 'num': 2}, {'name': 'a', 'grade': 1, 'student': True, 'num': 3}, {'name': 'b', 'grade': 1, 'student': True, 'num': 4}, {'name': 'b', 'grade': 1, 'student': True, 'num': 5}, {'name': 'b', 'grade': 1, 'student': True, 'num': 6}, {'name': 'c', 'grade': 3, 'student': True, 'num': 7}, {'name': 'c', 'grade': 3, 'student': True, 'num': 8}]
  • dict_test.pyを少し書き換えたプログラムdict_test2.pyを実行した。今回質問するに至った問題が再現された。

Python

1# dict_test2.py 2dic_list = [] 3a = {"name":"a", "grade":1, "student":True} 4b = {"name":"b", "grade":1, "student":True} 5c = {"name":"c", "grade":3, "student":True} 6 7for i in range(4): 8 dic_list.append(a) 9 10for i in range(3): 11 dic_list.append(b) 12 13for i in range(2): 14 dic_list.append(b) 15 16for i in range(len(dic_list)): 17 dic_list[i]["num"] = i 18 19print(dic_list)

Python

1# 実行結果 2[{'name': 'a', 'grade': 1, 'student': True, 'num': 3}, {'name': 'a', 'grade': 1, 'student': True, 'num': 3}, {'name': 'a', 'grade': 1, 'student': True, 'num': 3}, {'name': 'a', 'grade': 1, 'student': True, 'num': 3}, {'name': 'b', 'grade': 1, 'student': True, 'num': 8}, {'name': 'b', 'grade': 1, 'student': True, 'num': 8}, {'name': 'b', 'grade': 1, 'student': True, 'num': 8}, {'name': 'b', 'grade': 1, 'student': True, 'num': 8}, {'name': 'b', 'grade': 1, 'student': True, 'num': 8}]

補足情報(FW/ツールのバージョンなど)

  • Python 3.10.4

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

自己解決となっていますが、コメントします。

この件はpython(他でもありますが)のプログラミングあるあるの筆頭でしょう。

pythonでは値はオブジェクトであり、何らかの方法で生成されます。

python

1 card_info = {"name":card_name, "color":color_read(card_info_list[0]), "tap":False, "select":False, "position":[], "reverse":False}

上のコードでは、辞書が一つ生成されてcard_info変数に代入されていまが、ここで大事なのは、変数card_infoに値を入れているわけではないことを理解することです。 辞書はどこかに作られていて、それを変数からアクセスできるようになっているのです。
card_ifno ---> {辞書データ}
こんな感じです。

変数に値が入っていることとの違いは、変数間で値を受け渡すときです。

python

1xx = card_info

としたとき、xx に card_infoが持つ辞書を代入していますが、このとき、変数は入れ物ではないので、オブジェクトのコピーは起きません。 この処理の結果は、 card_info と xx が同じオブジェクトを指すようになるということです。
card_info ---> {辞書データ}
xx -----------↑
この状態で、card_ifno を変更すると xx の値も変更されます。 同じものを指しているので当然です。

今変数xx で説明したことは、 配列や辞書の値についても同様です。

pyth

1 for i in range(card_value): 2 MAIN_deck.append(card_info)

この処理で、 MAIN_deckリストにappendされているのは、 card_infoが指している辞書のコピーではなく、同じものへのリンクのようなものです。 同じ辞書を指しているので、1つ変更すればすべて変ってしまいます。

解決策の1つは、 appendするものを、その都度生成する方法です。 自己解決の方法はそれです。
説明に何度か書いたように、コピーではないのが問題なので、コピーするという 方法もあります。

pyth

1 for i in range(card_value): 2 MAIN_deck.append(card_info.copy())

だだし、今回のコピーは浅い(shallow)コピーなので、場合によっては深い(deep)コピーが必要になるかもしれません。

投稿2023/03/11 07:52

TakaiY

総合スコア13492

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

union_15

2023/03/11 08:17

ありがとうございます。 変数に値が入っているのかリンクが入っているのかという考え方は私の頭にはなかったため、非常に参考になりました。 また、そういった内部の構造、処理についてもっと勉強しなければとやる気が湧きました。 お教えくださり感謝いたします。
guest

0

自己解決

「試したこと」の欄が的を得ていた。
コードの95~99行目を以下のように置換したところ、想定通りの結果が得られた。

Python

1 # カードデータの完成 2 for i in range(card_value): 3 MAIN_deck.append({"name":card_name, "color":color_read(card_info_list[0]), "tap":False, "select":False, "position":[], "reverse":False})

元のコードでは格納するデータを一度変数に代入してからその変数をリストに格納しており、どうやらこれがよくなかったらしい。
変更後のコードでは格納するデータをそのままリストに格納している。

投稿2023/03/11 07:22

union_15

総合スコア1

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.40%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問