2

次のアプローチを使用して、MySQL データベースから行を辞書として (SSDictCursor を使用して) プルし、いくつかの処理を行っています。

from collections import namedtuple

class Foo(namedtuple('Foo', ['id', 'name', 'age'])):
    __slots__ = ()

    def __init__(self, *args):
        super(Foo, self).__init__(self, *args)

    # ...some class methods below here

class Bar(namedtuple('Bar', ['id', 'address', 'city', 'state']):
    __slots__ = ()

    def __init__(self, *args):
        super(Bar, self).__init__(self, *args)

    # some class methods here...

# more classes for distinct processing tasks...

を使用するnamedtupleには、必要なフィールドを事前に正確に把握する必要がありますが、これで問題ありません。ただし、ユーザーが単純なSELECT *ステートメントをプログラムに入力できるようにしたいと考えています。プログラムは、結果セットの行を反復処理し、これらの異なるクラスを使用して複数のタスクを実行します。これを機能させるために、私のクラスは、カーソルから入ってくる N 個のフィールドをどうにかして調べ、namedtuple定義によって期待される名前に対応する特定のサブセット M < N のみを取得する必要があります。

私が最初に考えたのは、各クラスに適用できる単一のデコレータを作成してみることでした。このデコレータは、クラスを調べて期待されるフィールドを確認し、適切な引数のみを新しいオブジェクトに渡します。しかし、ここ数日でデコレータについて読み始めたばかりで、まだ自信がありません。

だから私の質問は2つの部分に分かれています:

  1. これは、装飾されている特定のクラスに必要なフィールドを特定する単一のデコレータで行うことは可能ですか?
  2. 同じ機能を備えた、使いやすく、変更しやすく、理解しやすい代替手段はありますか?

各結果セットに何百万行もあるテーブルとフィールドの潜在的な順列が多すぎるため、1 つの汎用namedtupleサブクラスを記述してそれぞれの異なるタスクに対処することはできません。クエリ時間と使用可能なメモリが制限要因であることが証明されています。

必要に応じて:

>>> sys.version
'2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)]'
4

3 に答える 3

3

まず、作成__new__をカスタマイズするためにオーバーライドする必要があります。これは、のメソッドが に到達する前にその引数をチェックするためです。namedtuplenamedtuple__new____init__

第二に、あなたの目標がキーワード引数を受け入れてフィルタリングすることである場合**kwargs*args.

だから、それをまとめる:

class Foo(namedtuple('Foo', ['id', 'name', 'age'])):
    __slots__ = ()

    def __new__(cls, *args, **kwargs):
        kwargs = {k: v for k, v in kwargs.items() if k in cls._fields}
        return super(Foo, cls).__new__(cls, *args, **kwargs)

その dict 内包表記を に置き換えることもできますがitemgetter、itemgetter を複数のキーで使用するたびに、その意味を理解する人は誰もいないので、しぶしぶ使用をやめました。


インスタンスを返す__init__とすぐに呼び出されるため、理由があればオーバーライドすることもできます。__new__Foo

ただし、namedtuple__init__は引数を取らず、何もしないため、これだけを行う必要はありません。値はすでに設定されています__new__(tupleやその他の不変型と同様)。CPython 2.7 のように見えますが、実際に super(Foo, self).__init__(*args, **kwargs)無視されますが、PyPy 1.9 と CPython 3.3 では TypeError が発生します。いずれにせよ、それらを渡す理由はありませんし、それが機能するとは何も言っていません。

__init__unfiltered を取得することに注意してくださいkwargs。それを変更したい場合は、新しい辞書を作成する代わりに、そのkwargs場で変更できます。__new__しかし、それで何かができるという保証はまだないと私は信じています。フィルタリングされていない引数を取得するか、フィルタリングされていないかを保証するのではなく、実装定義にするだけです。


それで、これをまとめてもらえますか?もちろん!

def LenientNamedTuple(name, fields):
    class Wrapper(namedtuple(name, fields)):
        __slots__ = ()
        def __new__(cls, *args, **kwargs):
            args = args[:len(fields)]
            kwargs = {k: v for k, v in kwargs.items() if k in fields}
            return super(Wrapper, cls).__new__(cls, *args, **kwargs)
    return Wrapper

これには、準プライベート/半文書化_fieldsクラス属性を使用する必要がないという利点があることに注意してください。これはfields、パラメーターとして既に持っているためです。

また、コメントで提案されているように、余分な位置引数を捨てる行を追加しました。


を使用するのと同じように使用するだけでnamedtuple、余分な引数は自動的に無視されます。

class Foo(LenientNamedTuple('Foo', ['id', 'name', 'age'])):
    pass

print(Foo(id=1, name=2, age=3, spam=4))

    print(Foo(1, 2, 3, 4, 5)) print(Foo(1, 年齢=3, 名前=2, 卵=4))


テストをアップロードし、2.6 との互換性のためにdict 内包dict()表記を Geneexpr に置き換えました (2.6 が の最も古いバージョンですnamedtuple) が、引数の切り捨てはありません。CPython 2.6.7、2.7.2、2.7.5、3.2.3、3.3.0、および 3.3.1、PyPy 1.9.0 では、順不同のキーワードを含む、位置、キーワード、および混合引数で動作します。および 2.0b1、および Jython 2.7b。

于 2013-07-12T19:34:00.020 に答える
3

型には、オブジェクト内のフィールドの名前のタプルであるnamedtuple属性があります。_fieldsこれを使用して、データベース レコードから必要なフィールドを掘り出すことができます。

于 2013-07-12T19:22:01.130 に答える