Pythonで辞書を再発明する方法

Djangoアプリケーションでは、ボーナスのレポート(計算)を作成する必要がありました。

レポートは、ユーザー、部門、および会社全体の結果の要約を含むネスト構造にする必要があります。 概略的に、そのロジックは次のように表現できます。



print total for department in departments: print department.total for user in department.users: print user.total for row in user.rows: print row.data
      
      





このレポートには、2つの複雑な点がありました。



  1. 異なるモデル(および間隔を空けることができる)が「行」として機能する可能性がありますが、これはQuerySetsに対する反復子の使用を許可しません。
  2. 建設時間を報告します。 データの収集にはかなりの時間がかかります(数秒)。 レポートのデータは変更される場合があります。 清潔さといえば、これは静的なレポートではなく、レポートの形で発生したボーナスを監視および調整するためのツールです。 ただし、データはそれほど頻繁には変更されません。たとえば、100ビューごとに1つの変更があり、その後レポートを再構築する必要があります。 つまり データをキャッシュできます。


埋め込み辞書の構造は、両方の問題を完全に解決します。必要なすべてのスカラー(数値、行、日付)を追加し、シリアル化してキャッシュすることができます。



レポートのデータ構造がフォームを取得しました(簡略化):



 { 'total': { 'income': 1234, 'bonus': 123, 'expense': 1234, 'penalty': 123 }, 'departments': { '{dept_id}': { 'department': { 'title': 'Mega Department' } 'total': { 'income': 1234, 'bonus': 123, 'expense': 1234, 'penalty': 123 }, 'users': { '{user_id}': { 'user': { 'name': 'John Smith' }, 'total': { 'income': 1234, 'bonus': 123, 'expense': 1234, 'penalty': 123 }, 'rows': { '{sale_id}': { //   'type': 'sale' 'base_income': 1234, 'bonus': 123, 'comment': 'some description' }, '{expense_id}': { //   !!! 'type': 'expense' 'expense': 1234, 'penalty': 123, 'comment': 'some description' }, ... } }, ... } }, ... } }
      
      





そして、ここで私は辞書からそのような構造を記入することは私が望むほど便利ではないという問題に直面しました。 辞書でキーをチェックするか、setdefatult(key、{})を使用すると、コードが読みにくい混乱に変わります。



この構造は、XMLに似ています。 そして、XPath式がXMLツリーのノードをアドレス指定する方法に似たものを使用したいと思います。



 /departments/{dept_id}/users/{user_id}/rows/{row_id}/base_income
      
      





またはPythonでは次のようになります:



 data.departments.{dept_id}.users.{user_id}.rows.{row_id}.base_income
      
      





{dept_id}およびその他の{id}は整数であることに留意して、角括弧[]を使用することを許可しました。



 data.departments[{dept_id}].users[{user_id}].rows[{row_id}].base_income
      
      





実際には、基本的に辞書のように振る舞うクラスが必要でしたが、同時に:



  1. 角括弧なしで属性にアクセスできます
  2. 欠落している属性は自動的に作成されました


これがElasticDict生まれた方法です。



最後に



データ準備コードは次のようになります。



 data = ElasticDict() for sale in Sale.objects.filter(...).prefetch_related(...): data.departments[sale.user.department.pk].users[sale.user.pk].rows[sale.pk] = {'base_income': sale.amount, 'bonus': sale.calc_bonus()} #    ,     for expense in Expense.objects.filter(...).prefetch_related(...): data.departments[sale.user.department.pk].users[sale.user.pk].rows[expense.pk].base_expense = expense.amount data.departments[sale.user.department.pk].users[sale.user.pk].rows[expense.pk].penalty = expense.calc_penalty()
      
      





テンプレートのコードは次のとおりです。



 {{ data.total }} {% for dept_id, department in data.departments.items %} {{ department.total }} {% for user_id, user in department.users.items %} {{ user.total }} {% for row_id, row in user.rows.items %}: {{ row.data }} {% endfor %} {% endfor %} {% endfor %}
      
      





おわりに



ElasticDict()は通常のdict() 'aのサブクラスであることに注意してください。 通常の辞書のように、すべてが利用できます。 構造を「修正」する必要があるとき(存在しないキーにアクセスするときにKeyErrorを受け取りたい)、ElasticDictインスタンスを通常のdict()にエクスポートできます。 ElasticDict()の再帰ツアーが行われ、このクラスのすべてのインスタンスが通常の辞書に置き換えられます。 逆変換もあります-入力で辞書を入力し、出力でElasticDictを再帰的な走査で取得します。



コメント/提案を歓迎します!



英語圏からの更新は、すでにアナログ中毒者がいることを示唆しました。 「する必要がある」と投票した人は、より安定した(テスト済みの)ものに切り替えるべきだと思います。




All Articles