「self.x = x; self.y = y; __init__のself.z = z”パターン?


170

次のようなパターンが表示されます

def __init__(self, x, y, z):
    ...
    self.x = x
    self.y = y
    self.z = z
    ...

かなり頻繁に、多くの場合、より多くのパラメーターを使用します。このような退屈な繰り返しを避ける良い方法はありますか?クラスはnamedtuple代わりに継承する必要がありますか?


31
すべての受容性が悪いわけではありません。Pythonのクラスモデルにはインスタンス属性の明示的な定義が含まれていないため、これらの割り当ては自己文書化された同等物です。
chepner 2016

4
@chepner:まあ、明示的な定義は必要ありません。__slots__ただし、目的に使用できます。それは穏やかに非Pythonic(メモリを節約するためにより詳細)ですが、名前を入力した場合にまったく新しい属性が自動的に活性化されるリスクを回避するために、私はそれを大いに気に入っています。
ShadowRanger 2016

2
優れたエディタにはテンプレートがあります。入力ini <shortcut> x, y, z): <shortcut>すれば完了です。
Gerenuk 2016

3
名前付きタプルは、不変の値オブジェクトが必要な場合に最適です。通常の変更可能なクラスが必要な場合は、使用できません。
RemcoGerlich 2016

4
「しない」は適切なオプションです。使用可能なオプションは、メソッドシグネチャを(したがって、場合によってはインターフェース全体を)キルします。さらに、クラスに初期化するのに耐えられないほど多くのフィールドがある場合は、それらを分割することを検討してください。
Kroltan 2016

回答:


87

編集:あなたのpythonを持っている場合は3.7+だけ使用データクラスを

署名を保持するデコレータソリューション:

import decorator
import inspect
import sys


@decorator.decorator
def simple_init(func, self, *args, **kws):
    """
    @simple_init
    def __init__(self,a,b,...,z)
        dosomething()

    behaves like

    def __init__(self,a,b,...,z)
        self.a = a
        self.b = b
        ...
        self.z = z
        dosomething()
    """

    #init_argumentnames_without_self = ['a','b',...,'z']
    if sys.version_info.major == 2:
        init_argumentnames_without_self = inspect.getargspec(func).args[1:]
    else:
        init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]

    positional_values = args
    keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
    attribute_values = positional_values + keyword_values_in_correct_order

    for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
        setattr(self,attribute_name,attribute_value)

    # call the original __init__
    func(self, *args, **kws)


class Test():
    @simple_init
    def __init__(self,a,b,c,d=4):
        print(self.a,self.b,self.c,self.d)

#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
    print(inspect.getargspec(Test.__init__).args)
else:
    print(inspect.signature(Test.__init__))

2
いい答えですが、python2.7では機能しません:いいえsignature
MaxB

3
@alexis "decorator.decorator"デコレータは関数を自動的にラップします
Siphor

4
これが好きなのか嫌いなのか私はかなり引き裂かれています。署名を保存していただきありがとうございます。
カイルストランド

14
「...明示的なものは暗黙的なものよりも優れています。単純なものは複雑なものよりも優れています。...」
Jack Stout

9
-1率直に言って、これは恐ろしいことです。このコードが何をしているのか一目でわかりません。文字通りコードの10倍です。賢いことはクールですべてのことを感じますが、これはあなたの明らかな賢さの誤用です。
Ian Newson、2016

108

免責事項:この解決策を提示することに懸念を抱いている人がいるようですので、私は非常に明確な免責事項を提供します。このソリューションは使用しないでください。私はそれを情報としてのみ提供するので、あなたは言語がこれを可能にすることを知っています。残りの答えは言語機能を示すだけであり、このように使用することを推奨するものではありません。


パラメータを属性に明示的にコピーすることに何の問題もありません。ctorのパラメーターが多すぎる場合、コードの匂いと見なされることがあり、これらのパラメーターをより少ないオブジェクトにグループ化する必要があります。それ以外の場合、それは必要であり、それで何も問題はありません。とにかく、それを明示的に行うことは行く方法です。

ただし、どのように実行できるか(実行する必要があるかどうかではなく)を尋ねているので、1つの解決策は次のとおりです。

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

a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2

16
良い答え+1 ... self.__dict__.update(kwargs)少しパイソン的かもしれませんが
Beasley

44
このアプローチの問題A.__init__は、引数が実際に期待するものの記録がなく、タイプミスした引数名のエラーチェックがないことです。
MaxB 2016

7
@JoranBeasleyインスタンス辞書を盲目的に更新kwargsすると、SQLインジェクション攻撃に相当します。オブジェクトに名前付きのメソッドがmy_methodありmy_method、コンストラクタに名前付きの引数を渡した場合update()、ディクショナリはメソッドを上書きしただけです。
ペドロ

3
他の人が言ったように、提案は本当に貧弱なプログラミングスタイルです。重要な情報を隠します。表示することはできますが、OPを使用しないように明示的に推奨する必要があります。
Gerenuk 2016

3
@Pedro gruzczyの構文とJoranBeasleyの構文の間に意味上の違いはありますか?
gerrit

29

他の人が述べたように、繰り返しは悪くありませんが、場合によっては名前付きタプルがこの種の問題に非常に適していることがあります。これは、通常悪い考えであるlocals()やkwargsの使用を避けます。

from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")

# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z

使い方は限られていますが、他のオブジェクトと同じように名前付きタプルを継承できます(例は続きます)。

class MySuperXYZ(XYZ):
    """ I add a helper function which returns the original properties """
    def properties(self):
        return self.x, self.y, self.z

abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()

5
これらタプルであるため、propertiesメソッドをjustのように記述できますreturn tuple(self)。これは、将来、namedtuple定義にフィールドがさらに追加された場合により保守しやすくなります。
PaulMcG 2016

1
また、namedtuple宣言文字列はフィールド名の間にカンマを必要とせずXYZ = namedtuple("XYZ", "x y z")、同様に機能します。
PaulMcG 2016

@PaulMcGuireに感謝します。私は、継承とそれに間隔をあけたものを示す非常に単純なアドオンを考えていました。あなたは100%正解であり、他の継承オブジェクトとの優れた速記でもあります。私は、フィールド名はコンマまたはスペースを分離することができ言及しない-私は習慣からCSVを好む
A小さなシェルスクリプト

1
私はしばしばnamedtupleこの正確な目的でsを使用します。特に、関数が高度にパラメーター化されていて、一緒にしか意味をなさない係数の束がある数学コードでは特にそうです。
2016

の問題namedtupleは、それらが読み取り専用であることです。そんなことはできませんabc.x += 1
hamstergene 2016

29

暗黙的よりも明示的の方がよい...したがって、より簡潔にすることができます。

def __init__(self,a,b,c):
    for k,v in locals().items():
        if k != "self":
             setattr(self,k,v)

よりよい質問はあなたがすべきですか?

...名前付きタプルが必要な場合は、名前付きタプルを使用することをお勧めします(タプルには特定の条件が関連付けられていることに注意してください)...おそらく、ordereddictまたは単なるdictが必要です...


次に、オブジェクトは属性としてそれ自体を持っているため、循環ガベージコレクションが必要になります
John La Rooy 2016

3
@bernie(またはそれはbemieですか?)、時々ke r ningは難しい

4
わずかに効率的なテストでは、文字列比較ではなく、安価なIDテストにif k != "self":変更できますif v is not self:。技術的に__init__は、構築後に2回目に呼び出してself後続の引数として渡すことができると思いますが、実際にはどのようなモンスターがそれを行うのか考えたくありません。:-)
ShadowRanger 2016

それはの戻り値を取る関数に作ることができますlocalsset_fields_from_locals(locals())。そして、それはより魔法のデコレータベースのソリューションよりも長くありません。
Lii

20

gruszczy答えを拡張するために、次のようなパターンを使用しました。

class X:
    x = None
    y = None
    z = None
    def __init__(self, **kwargs):
        for (k, v) in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
            else:
                raise TypeError('Unknown keyword argument: {:s}'.format(k))

次の理由でこの方法が好きです。

  • 繰り返しを避ける
  • オブジェクトを構築するときのタイプミスに強い
  • サブクラス化でうまく機能します(ちょうどsuper().__init(...)
  • ではなく、クラスレベル(属している場所)で属性のドキュメントを作成できます。 X.__init__

Python 3.6より前のバージョンでは、属性が設定される順序を制御できません。これは、一部の属性が他の属性にアクセスするセッターを持つプロパティである場合に問題になる可能性があります。

少し改善されるかもしれませんが、自分のコードを使用しているのは私だけなので、入力の衛生については心配していません。おそらくAttributeErrorより適切でしょう。


10

あなたも行うことができます:

locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
    setattr(self, arg, locs[arg])

もちろん、inspectモジュールをインポートする必要があります。


8

これは、追加のインポートなしのソリューションです。

ヘルパー機能

小さなヘルパー関数はそれをより便利で再利用可能にします:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    self = local_name_space.pop('self')
    for name, value in local_name_space.items():
        setattr(self, name, value)

応用

あなたはそれをlocals()次のように呼び出す必要があります:

class A:
    def __init__(self, x, y, z):
        auto_init(locals())

テスト

a = A(1, 2, 3)
print(a.__dict__)

出力:

{'y': 2, 'z': 3, 'x': 1}

変わらず locals()

変更したくない場合locals()は、このバージョンを使用してください:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    for name, value in local_name_space.items():
        if name != 'self': 
            setattr(local_name_space['self'], name, value)

docs.python.org/2/library/functions.html#locals locals()は変更しないでください(インタープリターに影響する場合があります。この場合、self呼び出し元の関数のスコープから削除されます)
MaxB

@MaxBあなたが引用するドキュメントから:...変更は、インタプリタが使用するローカル変数とフリー変数の値に影響を与えない場合あります。 selfは引き続きで利用できます__init__
マイク・ミュラー

そうです、読者はそれがローカル変数に影響を与えることを期待しますが、多くの状況に応じて、そうでないかもしれません。ポイントは、それがUBであることです。
MAXB

引用:「この辞書の内容は変更しないでください」
MaxB

@MaxB locals()を変更しないバージョンを追加しました。
マイク・ミューラー

7

これを処理する(そして他の多くのボイラープレートを回避する)興味深いライブラリはattrsです。あなたの例は、例えば、これに減らすことができます(クラスがと呼ばれていると仮定しますMyClass):

import attr

@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

他のことも行わない__init__限り、メソッドはもう必要ありません。これがGlyph Lefkowitzによる素晴らしい紹介です


attrによって冗長化された機能はどの程度dataclassesですか?
gerrit

1
@gerritこれはattrsパッケージのドキュメントで議論されています。Tbh、違いはもうそれほど大きくないようです。
Ivo Merchiers

5

私の0.02 $。Joran Beasleyの回答に非常に近いですが、よりエレガントです。

def __init__(self, a, b, c, d, e, f):
    vars(self).update((k, v) for k, v in locals().items() if v is not self)

さらに、MikeMüllerの答え(私の好みに最適なもの)は、この手法で減らすことができます。

def auto_init(ns):
    self = ns.pop('self')
    vars(self).update(ns)

そして、auto_init(locals())あなたからのちょうど電話__init__


1
docs.python.org/2/library/functions.html#locals locals()は変更しないでください(未定義の動作)
MaxB

4

これは、Pythonで物事を行うための自然な方法です。もっと賢いものを発明しようとしないでください。チームの誰も理解できない過度に賢いコードにつながります。チームプレーヤーになりたい場合は、この方法で書き続けてください。


4

Python 3.7以降

Python 3.7ではdataclassdataclassesモジュールから使用できるデコレータを(使用しない)で使用できます。ドキュメントから:

このモジュールは自動的ように生成特殊メソッド追加するためのデコレータと機能を提供__init__()し、__repr__()ユーザ定義のクラスにします。もともとはPEP 557に記述されていました。

これらの生成されたメソッドで使用するメンバー変数は、PEP 526型注釈を使用して定義されます。たとえば、次のコード:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

__init__()次のようなa を追加します。

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
      self.name = name
      self.unit_price = unit_price
      self.quantity_on_hand = quantity_on_hand

このメソッドはクラスに自動的に追加されることに注意してください。上記のInventoryItem定義では直接指定されていません。

クラスが大きくて複雑な場合、を使用するのは不適切な場合ありますdataclass。これはPython 3.7.0のリリース日に書いているので、使用パターンはまだ確立されていません。

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