クラスまたは辞書を使用する必要がありますか?


99

次のように、フィールドのみを含み、メソッドを含まないクラスがあります。

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

これは簡単に辞書に変換できます。クラスは、将来の追加に対してより柔軟であり、で高速になる可能性があり__slots__ます。では、代わりにdictを使用するメリットはありますか?辞書はクラスより速いでしょうか?スロットのあるクラスよりも速いですか?


2
私は常にデータを保持するために辞書を使用しており、これは私にとってはユースケースのように見えます。場合によっては、クラスを派生さdictせることは意味があります。きちんとした利点:デバッグするときは言うだけで、print(request)すべての状態情報がすぐに表示されます。より古典的なアプローチでは、カスタム__str__メソッドを作成する必要があります。これは、常に行う必要がある場合には面倒です。
フロー

これらは交換可能です。stackoverflow.com
questions / 1305532 /

クラスが完全に理にかなっていて、他の人にはっきりしている場合は、なぜですか さらに、たとえば共通のインターフェースを持つ多くのクラスを定義する場合、なぜそうしないのですか?しかし、PythonはC ++のような強力なオブジェクト指向の概念をサポートしていません。
MasterControlProgram 2017

3
@Ralf PythonがサポートしていないOOPは何ですか?
QWR

回答:


31

なぜこれを辞書にするのですか?利点は何ですか?後でコードを追加したい場合はどうなりますか?あなたの__init__コードはどこに行きますか?

クラスは、関連データ(および通常はコード)をバンドルするためのものです。

辞書は、キーと値の関係を格納するためのものです。通常、キーはすべて同じタイプであり、すべての値も1つのタイプです。場合によっては、キー/属性名がすべてわかっていない場合にデータをバンドルするのに役立つことがありますが、多くの場合、これは設計に問題があることを示しています。

これをクラスにしてください。


クラスの__init__メソッドの代わりに、dictを作成する一種のファクトリメソッドを作成します。しかし、あなたは正しいです。私は一緒に属するものに分離します。
デーモン

88
辞書、セット、リスト、タプルがすべて揃っており、関連するデータをまとめることができます。まったく逆に、ディクショナリの値が同じデータ型でなければならない、またはそうでなければならないという仮定は決してありません。多くのリストとセットでは、値は同じ型になりますが、それは主にそれを一緒に列挙したいためです。実際、データを保持するためにクラスを広く使用することは、oopの乱用だと思います。シリアル化の問題について考えると、その理由が簡単にわかります。
フロー

4
オブジェクト指向プログラミングであり、クラス指向ではない理由があります。オブジェクトを扱うからです。オブジェクトは2(3)のプロパティで特徴付けられます。1。状態(メンバー)2.動作(メソッド)および3.インスタンスはいくつかの単語で説明できます。したがって、クラスは状態と動作をまとめるためのものです。
friendzis 2013

14
可能な場合は常にデフォルトでより単純なデータ構造にする必要があるため、これをマークダウンしました。この場合、目的の目的には辞書で十分です。質問where would your __init__ code go?は心配です。辞書でinitメソッドが使用されていないため、経験の浅い開発者にクラスのみを使用するように説得することができます。不条理。
ロイドムーア

1
@RalfクラスFooは、intやstringのような単なる型です。整数型または整数型の変数fooに値を格納しますか?微妙ですが、重要な意味の違い。Cのような言語では、この区別は学界の外ではあまり重要ではありません。ほとんどのオブジェクト指向言語はクラス変数をサポートしていますが、クラスとオブジェクト/インスタンスの区別は非常に重要です。データをクラス(すべてのインスタンスで共有)または特定のオブジェクトに格納しますか?かまれないでください
friendzis

44

クラスの追加のメカニズムが必要でない限り、辞書を使用してください。namedtupleハイブリッドアプローチにを使用することもできます。

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

ここでのパフォーマンスの違いはごくわずかですが、辞書が速くなければ驚かれるでしょう。


15
「ここでのパフォーマンスの違いは次のようになります最小限の辞書がなかった場合、私は驚かれることでしょうが、大幅に高速化。」これは計算しません。:)
mipadi 2010年

1
@mipadi:これは本当です。修正されました:p
Katriel

dictは、namedtupleより1.5倍高速で、スロットのないクラスの2倍高速です。この回答に関する私の投稿を確認してください。
alexpinho98 2013

@ alexpinho98:あなたが参照している「投稿」を見つけるために一生懸命努力しましたが、見つかりません!あなたはURLを提供できますか?ありがとう!
Dan Oblinger、2015年

@DanOblinger私は彼が以下の彼の答えを意味すると仮定しています。
Adam Lewis

37

Pythonでクラスがある辞書の下に。クラスの振る舞いには多少のオーバーヘッドがかかりますが、プロファイラーなしではそれに気づくことはできません。この場合、私はあなたがクラスから利益を得ると信じています:

  • すべてのロジックが単一の関数に存在します
  • 更新が簡単で、カプセル化されたままです
  • 後で何かを変更した場合、簡単にインターフェイスを同じに保つことができます

すべてのロジックが単一の関数に存在するわけではありません。クラスは共有状態のタプルであり、通常は1つ以上のメソッドです。クラスを変更した場合、そのインターフェースについての保証はありません。
ロイドムーア

25

それぞれの使用法は私にとって主観的すぎるので、それを理解することはできないと思うので、数値のみを使用します。

辞書で変数を作成および変更するのにかかる時間、new_styleクラス、およびスロット付きのnew_styleクラスを比較しました。

テストに使用したコードは次のとおりです(少し面倒ですが、機能します)。

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

そしてここに出力があります...

作成...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

変数を変更しています...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

したがって、変数を格納するだけで、速度が必要で、多くの計算を行う必要がない場合は、dictを使用することをお勧めします(メソッドのように見える関数を常に作成できます)。ただし、本当にクラスが必要な場合は、常に__ スロット __を使用してください。

注意:

「クラス」をnew_styleクラスとold_styleクラスの両方でテストしました。old_styleクラスは作成が速いが変更が遅いことがわかります(タイトなループで多くのクラスを作成している場合はそれほど重要ではありませんが重要です(ヒント:間違っている))。

また、私のものは古くて遅いので、変数を作成したり変更したりする時間はあなたのコンピューター上で異なるかもしれません。自分でテストして、「実際の」結果を確認してください。

編集:

私は後でnamedtupleをテストしました。変更することはできませんが、10000サンプル(またはそのようなもの)を作成するのに1.4秒かかったため、辞書が本当に最速でした。

私は場合は辞書機能を変更するキーと値を含めるようにし、代わりに辞書を含む変数の辞書を返すために、私はそれを作成するとき、それは私に与え代わりに0.8秒の0.65を。

class Foo(dict):
    pass

作成はスロットを持つクラスのようなもので、変数の変更が最も遅い(0.17秒)ので、これらのクラスは使用しないでください。辞書(速度)またはオブジェクトから派生したクラス(「構文キャンディ」)に行く


dict(オーバーライドされたメソッドがないと思いますか?)のサブクラスの数を確認したいと思います。最初から作成された新しいスタイルのクラスと同じように機能しますか?
ベンジャミンホジソン

12

@adwに同意します。私は「オブジェクト」を(OOの意味で)辞書で表すことはありません。辞書は名前と値のペアを集約します。クラスはオブジェクトを表します。オブジェクトがディクショナリで表現されているコードを見たことがありますが、実際の形状は不明です。特定の名前/値が存在しない場合はどうなりますか?クライアントが何も入れないように制限するもの。または、何も取り出さないようにすること。ものの形は常に明確に定義されるべきです。

Pythonを使用する場合、作者が自分の足で自分を撃つための多くの方法が言語で許可されているため、規律を持って構築することが重要です。


9
SOの回答は投票順に並べ替えられ、投票数が同じ回答はランダムに並べられます。それで、「最後のポスター」であなたが誰を意味するのかを明確にしてください。
Mike DeSimone、

4
そして、フィールドだけがあり、機能がないものがオブジェクト指向の「オブジェクト」であることをどうやって知っていますか?
Muhammad Alkarouri、

1
私のリトマステストは「このデータの構造は固定されていますか?」です。はいの場合は、オブジェクトを使用します。それ以外の場合は、dictを使用します。これから離れると混乱が生じるだけです。
weberc2 2016年

解決している問題のコンテキストを分析する必要があります。オブジェクトがそのランドスケープに慣れたら、比較的明確になるはずです。個人的には、返すのは名前と値のペアのマッピングでなければならない場合を除いて、ディクショナリを返すことが最後の手段であるべきだと思います。メソッドに渡されたり、メソッドから渡されたりするすべてのものが単なる辞書である、ずさんなコードを見てきました。それは怠惰です。動的型付け言語が大好きですが、コードを明確かつ論理的にすることも好きです。すべてを辞書に入れることは、意味を隠す便利な場合があります。
ジェイデル

5

リクエストに関連するあらゆる種類の情報であるため、クラスをお勧めします。辞書を使用するのであれば、保存されたデータは本質的にはるかに類似していると思います。私が従う傾向があるガイドラインは、キーと値のペアのセット全体をループして何かを実行したい場合は、辞書を使用することです。それ以外の場合、データは明らかに基本的なキー->値のマッピングよりもはるかに多くの構造を持っているため、クラスの方がより良い代替手段になる可能性があります。

したがって、クラスに固執してください。


2
まったくそう思わない。辞書の使用を繰り返し処理するものに制限する理由はありません。マッピングを維持するためのものです。
Katriel、

1
もう少し詳しく読んでください。私が言った以上ループしたいかもしれない、でしょう。私にとって、辞書を使用することは、キーと値の間に機能的な大きな類似点があることを意味します。たとえば、年齢を含む名前。クラスを使用すると、さまざまな「キー」が意味が大きく異なる値を持つことになります。たとえば、クラスPErsonを取ります。ここで、 'name'は文字列であり、 'friends'はリスト、辞書、またはその他の適切なオブジェクトです。このクラスの通常の使用の一部として、これらすべての属性をループすることはありません。
スティグマ

1
クラスと辞書の区別は、前者が後者を使用して実装されている(「スロット」は耐えられない)という事実により、Pythonでは不明瞭になっていると思います。私が最初に言語を学んでいたとき、これが少し混乱したことを知っています(クラスがオブジェクトであり、したがっていくつかの神秘的なメタクラスのインスタンスであったという事実と共に)。
martineau

4

あなたが達成したいすべてがのobj.bla = 5代わりに構文キャンディのようなものであるobj['bla'] = 5場合、特にそれを何度も繰り返す必要がある場合は、マルティノーの提案のようにいくつかのプレーンなコンテナクラスを使用したいと思うかもしれません。それにもかかわらず、コードはかなり肥大化し、不必要に遅くなります。あなたはそれをそのように単純に保つことができます:

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

を使用namedtupleしてsまたはクラスに切り替えるもう1つの理由__slots__は、メモリ使用量である可能性があります。辞書はリスト型よりもかなり多くのメモリを必要とするので、これは考えるべきポイントかもしれません。

とにかく、あなたの特定のケースでは、現在の実装から切り替える動機はないようです。これらのオブジェクトを何百万も保持していないようなので、リストから派生した型は必要ありません。また、実際には内にいくつかの機能ロジックが含まれている__init__ため、も使用できませんAttrDict


types.SimpleNamespace(Python 3.3以降で使用可能)は、カスタムAttrDictの代替です。
クリスティアンCiupitu

4

ケーキを持って食べてもいいかもしれません。つまり、クラスと辞書の両方のインスタンスの機能を提供するものを作成できます。ActiveStateのDɪᴄᴛɪᴏɴᴀʀʏᴡɪᴛʜᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇᴀᴄᴄᴇssレシピとそれを行う方法についてのコメントを参照してください。

サブクラスではなく通常のクラスを使用することに決めた場合、Tɪᴍᴘʟᴇsɪᴍᴘʟᴇʙᴜᴛʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀᴏғᴀʙᴜɴᴄʜᴏғᴛᴜғғsᴄʟᴀ"のssレシピ(Alex Martelliによる)は非常に柔軟であり、その種のものに役立つことがわかりましたあなたがやっているように見えます(つまり、情報の比較的単純なアグリゲーターを作成します)。これはクラスであるため、メソッドを追加することで機能を簡単に拡張できます。

最後に、クラスメンバーの名前は正当なPython識別子である必要がありますが、ディクショナリキーはそうではないことに注意してください。キーはハッシュ可能なもの(文字列ではないものでも)になるため、ディクショナリはその点でより自由になります。

更新

object(はありません__dict__)という名前のクラス(はあります)というサブクラスSimpleNamespacetypesモジュールPython 3.3 に追加されましたが、これも別の代替手段です。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.