Pythonで変数引数(kwargs)からクラス属性を設定するにはどうすればよいですか


119

可変数の引数を取り、条件付きでクラス属性として設定するコンストラクター(または他の関数)を持つクラスがあるとします。

手動で設定することもできましたが、Pythonでは変数パラメーターが十分に一般的であるため、これを行うための一般的なイディオムが必要です。しかし、これを動的に行う方法がわかりません。

evalを使用した例がありますが、安全ではありません。私はこれを行う適切な方法を知りたいです-おそらくラムダで?

class Foo:
    def setAllManually(self, a=None, b=None, c=None):
        if a!=None: 
            self.a = a
        if b!=None:
            self.b = b
        if c!=None:
            self.c = c
    def setAllWithEval(self, **kwargs):
        for key in **kwargs:
            if kwargs[param] != None
                eval("self." + key + "=" + kwargs[param])

これらの質問は似ているように見えます:stackoverflow.com/questions/3884612/... stackoverflow.com/questions/356718/... stackoverflow.com/questions/1446555/...それは私が欲しいもののように見えるようthis--自己多分です.__ dict__に[キー] = kwargs [キー]
fijiaaron

質問にはあまり関係ありませんが、従来のPythonスタイリングに関するいくつかのヒントについては、PEP8を確認することをお勧めします。
Thomas Orozco、

これにはattrsと呼ばれる素晴らしいライブラリがあります。単純にpip install attrs、クラスをで装飾し@attr.s、引数をa = attr.ib(); b = attr.ib()etcに設定します。詳細はこちらをご覧ください
Adam Barnes

ここで何か不足していますか?それでもself.x = kwargs.get'x 'を実行する必要があります。発信者からのタイプミスまで自分自身を開きます。追加の文字でクライアントを作成する必要があります。instance = Class(** {}) self.x = kwargs.get'x ']の平凡さ、とにかく後であなたに噛まれるのではないですか?つまり、self.xの代わりに、最終的にself .__ dict __ ['x']になりますか?またはgetattr()自己よりも多くのタイピング。
JGFMK、2018年

回答:


146

__dict__キーワード引数を使用して、属性(辞書の形式でクラス属性を表す)を更新できます。

class Bar(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

その後、次のことができます。

>>> bar = Bar(a=1, b=2)
>>> bar.a
1

そして次のようなもので:

allowed_keys = {'a', 'b', 'c'}
self.__dict__.update((k, v) for k, v in kwargs.items() if k in allowed_keys)

事前にキーをフィルタリングすることもできます(Python 2.xを使用している場合iteritemsitems、代わりに使用してください)。


2
self.__dict__.update(locals())位置引数もコピーする場合に使用するとさらに便利です。
ジャンカルロスポルテリ

2
最近必要になると思います.. kwargs.iteritems()の代わりにkwargs [0] .items()-(執筆時にPython 3.6.5を使用しています)
JGFMK

@JGFMKなぜkwargs[0]ただではなくkwargskwargs整数キーを持つこともできますか?私はそれらが文字列でなければならないことをかなり確信しています。
wjandrea

145

次のsetattr()方法を使用できます。

class Foo:
  def setAllWithKwArgs(self, **kwargs):
    for key, value in kwargs.items():
      setattr(self, key, value)

getattr()属性を取得するための類似の方法があります。


@larsksありがとうございますが、どのようにして辞書キーのみをアンパックできるのでしょうか?stackoverflow.com/questions/41792761/...
JinSnow

使用する必要があります.getattr()か?または、属性にアクセスできますFoo.keyか?
Stevoisiak 2018年

@StevenVascellaroはもちろん使用できますFoo.attrname。私はそのgetattr方法が存在するという事実を指摘していたと思います。また、名前付き属性が使用できない場合のデフォルト値を提供する場合にも役立ちます。
larsk、

3
受け入れられた答えとの違いは何ですか?。彼らの長所と短所は何ですか?
Eduardo Pignatelli、

15

ここでのほとんどの回答は、許可されたすべての属性を1つのデフォルト値に初期化するための適切な方法をカバーしていません。したがって、@ fqxpおよび@mmjによって与えられた回答に追加するには:

class Myclass:

    def __init__(self, **kwargs):
        # all those keys will be initialized as class attributes
        allowed_keys = set(['attr1','attr2','attr3'])
        # initialize all allowed keys to false
        self.__dict__.update((key, False) for key in allowed_keys)
        # and update the given keys by their given values
        self.__dict__.update((key, value) for key, value in kwargs.items() if key in allowed_keys)

これはへの初期化のため、最も完全な答えだと思いますFalse。いい視点ね!
キロール2017

9

fqxpの回答のバリエーションを提案します。これにより、許可された属性に加えて、属性のデフォルト値を設定できます。

class Foo():
    def __init__(self, **kwargs):
        # define default attributes
        default_attr = dict(a=0, b=None, c=True)
        # define (additional) allowed attributes with no default value
        more_allowed_attr = ['d','e','f']
        allowed_attr = list(default_attr.keys()) + more_allowed_attr
        default_attr.update(kwargs)
        self.__dict__.update((k,v) for k,v in default_attr.items() if k in allowed_attr)

これはPython 3.xコードです。Python2.xの場合iteritems()、の代わりに少なくとも1つの調整が必要ですitems()


1
これが最も柔軟な答えであり、このスレッドの他のアプローチを要約しています。属性を設定し、デフォルト値を許可し、許可された属性名のみを追加します。ここに示すように、python 3.xで正常に動作します。
squarespiral

7

mmjおよびfqxpによる優れた回答に基づくさらに別のバリアント。もし私たちがしたい場合

  1. 許可された属性のリストをハードコーディングしないでください
  2. 直接コンストラクターの各属性のデフォルト値をかつ明示的に設定する
  3. 次のいずれかにより、kwargsを事前定義された属性に制限します
    • 無効な引数を黙って拒否する、または、あるいは、
    • エラーが発生します。

「直接」とは、無関係なdefault_attributes辞書を回避することを意味します。

class Bar(object):
    def __init__(self, **kwargs):

        # Predefine attributes with default values
        self.a = 0
        self.b = 0
        self.A = True
        self.B = True

        # get a list of all predefined values directly from __dict__
        allowed_keys = list(self.__dict__.keys())

        # Update __dict__ but only for keys that have been predefined 
        # (silently ignore others)
        self.__dict__.update((key, value) for key, value in kwargs.items() 
                             if key in allowed_keys)

        # To NOT silently ignore rejected keys
        rejected_keys = set(kwargs.keys()) - set(allowed_keys)
        if rejected_keys:
            raise ValueError("Invalid arguments in constructor:{}".format(rejected_keys))

大きな進歩ではありませんが、誰かに役立つかもしれません...

編集: クラスが@propertyデコレーターを使用して「保護された」属性をゲッターとセッターでカプセル化し、これらのプロパティをコンストラクターで設定できるようにしたい場合は、次のように、allowed_keysからの値でリストを展開することができdir(self)ます。

allowed_keys = [i for i in dir(self) if "__" not in i and any([j.endswith(i) for j in self.__dict__.keys()])]

上記のコードは除外します

  • 隠し変数dir()( "__"の存在に基づく除外)、および
  • からのdir()属性名(保護されているかどうかにかかわらず)の最後に名前が見つからない__dict__.keys()メソッド。これにより、@ property修飾されたメソッドのみが保持される可能性があります。

この編集は、Python 3以降でのみ有効です。


2
class SymbolDict(object):
  def __init__(self, **kwargs):
    for key in kwargs:
      setattr(self, key, kwargs[key])

x = SymbolDict(foo=1, bar='3')
assert x.foo == 1

クラスSymbolDictは基本的に文字列ではなく記号を使用して動作する辞書であるため、このクラスを呼び出しました。言い換えれば、あなたはx.foo代わりに行いますx['foo']が、カバーの下でそれは実際に起こっている同じことです。


2

次の解決策vars(self).update(kwargs)またはself.__dict__.update(**kwargs)、ユーザーがエラーメッセージが表示されずに任意の辞書を入力することができますので、堅牢ではありません。ユーザーが次の署名( 'a1'、 'a2'、 'a3'、 'a4'、 'a5')を挿入していることを確認する必要がある場合、ソリューションは機能しません。さらに、ユーザーは、「位置パラメーター」または「kay-valueペアパラメーター」を渡すことでオブジェクトを使用できる必要があります。

したがって、メタクラスを使用して次のソリューションを提案します。

from inspect import Parameter, Signature

class StructMeta(type):
    def __new__(cls, name, bases, dict):
        clsobj = super().__new__(cls, name, bases, dict)
        sig = cls.make_signature(clsobj._fields)
        setattr(clsobj, '__signature__', sig)
        return clsobj

def make_signature(names):
    return Signature(
        Parameter(v, Parameter.POSITIONAL_OR_KEYWORD) for v in names
    )

class Structure(metaclass = StructMeta):
    _fields = []
    def __init__(self, *args, **kwargs):
        bond = self.__signature__.bind(*args, **kwargs)
        for name, val in bond.arguments.items():
            setattr(self, name, val)

if __name__ == 'main':

   class A(Structure):
      _fields = ['a1', 'a2']

   if __name__ == '__main__':
      a = A(a1 = 1, a2 = 2)
      print(vars(a))

      a = A(**{a1: 1, a2: 2})
      print(vars(a))

1

彼らはより良い解決策かもしれませんが、私にとって頭に浮かぶのは:

class Test:
    def __init__(self, *args, **kwargs):
        self.args=dict(**kwargs)

    def getkwargs(self):
        print(self.args)

t=Test(a=1, b=2, c="cats")
t.getkwargs()


python Test.py 
{'a': 1, 'c': 'cats', 'b': 2}

私が探しているのは、検証に基づいて条件付きで属性を設定することです。kwargsを使用する際の問題は、どの属性が受け入れ可能かを検証(または文書化)しないことであることに
気付きました

ええ、@ larsksの回答の方が優れていると思います。SOで毎日新しいことを学びましょう!
トム

1

これはラースク経由で最も簡単です

class Foo:
    def setAllWithKwArgs(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

私の例:

class Foo:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

door = Foo(size='180x70', color='red chestnut', material='oak')
print(door.size) #180x70

smbはkwargs.items()とは何かを説明できますか?
Oleg_Kornilov

kwargsキーワード引数の辞書でitems()あり、辞書の(key, value)ペアのリストのコピーを返すメソッドです。
ハリスコール2017年

-1

ほとんどの場合、名前付き引数を使用する方が(自己文書化コードを改善するため)、次のようになると思います。

class Foo:
    def setAll(a=None, b=None, c=None):
        for key, value in (a, b, c):
            if (value != None):
                settattr(self, key, value)

この反復は機能しません:for key, value in (a, b, c)
rerx
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.