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

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

ただいまの
回答率

87.59%

Flask / psycopg2でfetchallを利用した際に取得されるデータの構造がわからない

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,848

score 8

実現したいこと

・Flask及びpsycopg2にてfetchallを利用した際、取得したデータの型を知りたい

現状

下記のソースコードを元にpython,Flask,PostgreSQLについて学習しております。
その過程で、なぜそのコードで希望した出力がなされるのか、がわからなくなってしまったため、質問させていただけますと幸いです。

ソースコード及び不明点

#モジュールのインポート
from flask import Flask, render_template, request
import psycopg2
import psycopg2.extras

#インスタンス化
app = Flask(__name__)

#コネクションの作成
conn = psycopg2.connect("""
    user=flasklearner
    dbname=flasknote
""")

#カーソルの作成
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 

#ルーティング
@app.route('/', methods=['GET'])
def index():
    cur.execute('SELECT * FROM messages')
    messages = cur.fetchall()
    messages = [ dict(message) for message in messages ]
    return render_template('index.html', messages=messages)
<!doctype html>
<html>
  <head>
    <meta charset="utf-8"/>
  </head>
  <body>
    <div>
      {% for msg in messages %}
      <h2>{{msg.username}}</h2>
      <p>{{msg.message}}</p>
      {% endfor %}
    </div>
  </body>
</html>
CREATE TABLE messages (
 username varchar(64),
 message varchar(140)
);


※上記のコードからは消していますが、データはフォームから入力されたものをINSERT INTOでレコード作成しています。

不明点としてはcur.fetchall()で取得したデータ型が何であるか、といった内容になります。
自分の現状の認識は、

cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 


の部分にて、カーソル作成時に通常配列形式で取得される情報を辞書型で返されるように指定し、

cur.execute('SELECT * FROM messages')
messages = cur.fetchall()


にてテーブルに存在する全てのレコードを取得、そしてそれをmessagesに格納している、と思っています。
【不明点1】ここでmessagesには下記のような各レコードごとの辞書データのリストが入っているのでしょうか?

[{"username":"入力した値1", "message":"入力した値1"}, {"username":"入力した値2", "message":"入力した値2"}, ... , {"username":"入力した値N", "message":"入力した値N"}]


【不明点2】またその後の下記部分では何を行い、最終的にindex.html側でmsg.usernameとして中身が取り出せるのか、もわかっておりません。。。

messages = [ dict(message) for message in messages ]

初歩的な質問で恐縮ですが、お力添えいただけますと幸いです。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

不明点 1 について

print() してみましょう!

>>> print(items[0])
[1, '0000000001', 'テスト商品1', datetime.datetime(2021, 1, 16, 11, 36, 48, 60724)]


なんとただのリストが返ってきました。カラム情報はどこへ行ってしまったのか……。ここで type() をしてみると

>>> type(items[0])
<class 'psycopg2.extras.DictRow'>


なにやら組み込みの list とは違うクラスがでてきましたね。これを dir() にかけてみましょう。

>>> dir(items[0])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setstate__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_index', 'append', 'clear', 'copy', 'count', 'extend', 'get', 'index', 'insert', 'items', 'keys', 'pop', 'remove', 'reverse', 'sort', 'values']


よーくみると keys というのが入ってます。リストっぽい見た目のくせして dict のメソッドを持っています!keys() を早速実行してみると

>>> items[0].keys()
<odict_iterator object at 0x7fcbe5cd5360>
>>> list(items[0].keys())
['id', 'item_cd', 'name', 'created']


出ました!カラム名です!こんな感じで各々の列からカラム名を参照できるんですね。ただこれだと列ごとにカラム名持ってて効率悪くない?と思いませんか?ここからはコード読みにいったら途中から C のコードになって諦めてしまったので霊感で語るんですが、多分内部でクエリごとのカラム名をキャッシュしておいて、カラム名をリクエストされたらそのキャッシュを返すような仕組みになっているんではなかろーかという予想です。実際のデータも for で回したり list にするまでは内部的には違うオブジェクト扱いで、高速化がなされている、というのが予想です。

不明点 2 について

前述のように各データは DictRow という特殊なオブジェクトになっているのでした。これを dict() にかけてみましょう。

>>> dict(items[0])
{'id': 1, 'item_cd': '0000000001', 'name': 'テスト商品1', 'created': datetime.datetime(2021, 1, 16, 11, 36, 48, 60724)}


なんと、ディクショナリが返ってきましたね。実は dict は一定の条件を満たせばオブジェクトも渡すことが出来ます。
組み込み型 — Python 3.9.1 ドキュメント
DictRow は __getitem__() メソッドと keys() を持っているので dict() に渡すとディクショナリにすることが出来ます。あとはリスト内包表記で各列の DictRow に対し dict をかけていけばディクショナリの配列として受け取ることが出来ます。このようにして Django で messages を for で回しながら msg で参照することが出来ます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2021/01/16 20:14

    A_kirisakiさん
    こちら前回に続き回答いただきありがとうございます!
    kirisakiさんに前回ご回答いただいたことで現在Flaskを勉強中です!

    いただいたご回答について、まずは何より詳細ご説明いただきありがとうございます!
    その上で疑問に思ったことがいくつかありますので、もしよろしければお教えいただけると嬉しいです・・・!

    【不明点1に関して】
    (質問1)
    ご回答にあったitemsはkirisakiさんの方で作成されたDBのレコードを下記のように受けとった変数でしょうか?
    ```Python
    cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
    cur.execute('SELECT * FROM [テーブル名]')
    items = cur.fetchall()
    ```

    (質問2)
    自分の方で書かせていただいたコードのmessagesは(また質問1が正であった際、kirisakiさんの方でご用意いただいたitemsは)list型でもdict型でもなく、psycopg2.extras.DictRow型というpsycopg2で定義している型(クラス)ということでしょうか?

    またpsycopg2.extras.DictRow型は要素番号を指定すると各レコードごとの値が取得でき、また.keysメソッドを利用することで各要素(各レコードの値)からカラム名のリスト(?)へアクセスできる、ということでしょうか?

    (質問3)※これはあまり本件とは関係ないかもです・・・
    今回のitemsのように処理自体がローカルサーバーを起動して行う場合(手元のエディターでコンパイルするのみではない場合?)、printの結果はどこに出力されるのでしょうか・・・?
    VScodeなどでコンパイルした際には標準出力でターミナルで確認できると思うのですが、今回のような場合もprintで結果がみれるならデバッグしやすくてよいな・・・と思ってます。

    質問が多くて恐縮です・・・

    キャンセル

  • 2021/01/16 20:22

    1. そうです。はしょちゃって伝わりにくかったですね。
    2. そういうことです。list であり dict でもあるみたいな特殊なクラスですね。
    3. あまり VSCode は触ったことがないので正確なことは言えないんですが、多分ターミナルが付いてると思うんでそこで python を起動しちゃいます。何もオプションがないと repl(Read-Eval-Print Loop) という対話型のシェルが開きます。そうすると例のように >>> と出るので変数を打ち込むと中身を表示してくれたりします。import とかもできるので自分が作った関数のテストとかも出来ます(でもそれは本当は自動テストを書いたほうがよいかも)。

    キャンセル

  • 2021/01/16 20:44

    kirisakiさんお返事ありがとうございます!
    質問3つともご回答いただき、そして内容も理解できました!
    いつも本当にありがとうございます!

    キャンセル

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

  • ただいまの回答率 87.59%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る