次のようなパターンが表示されます
def __init__(self, x, y, z):
...
self.x = x
self.y = y
self.z = z
...
非常に頻繁に、多くの場合より多くのパラメータを使用します。この種の退屈な繰り返しを避ける良い方法はありますか?クラスは代わりにnamedtuple
を継承する必要がありますか?
編集:python 3.7+がある場合は、 dataclasses を使用します
署名を保持するデコレータソリューション:
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__))
免責事項:このソリューションの提示に懸念を持っている人がいるようです。そのため、非常に明確な免責事項を提供します。このソリューションを使用しないでください。私はそれを情報として提供するだけなので、あなたはその言語がこれに対応していることを知っています。残りの答えは、このように使用することを推奨するのではなく、単に言語の機能を示すことです。
パラメーターを属性に明示的にコピーすることには、実際のところ何も問題はありません。 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
他の人が述べたように、繰り返しは悪くありませんが、名前付きタプルがこの種の問題にぴったりである場合があります。これにより、通常は悪い考えである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
用途は限られていますが、他のオブジェクトと同様にnamedtupleを継承できます(例は続きます)。
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()
明示的は暗黙的よりも優れているため、より簡潔にすることができます。
def __init__(self,a,b,c):
for k,v in locals().items():
if k != "self":
setattr(self,k,v)
より良い質問はあなたがすべきですか?
...名前付きタプルが必要な場合は、namedtupleを使用することをお勧めします(タプルには特定の条件が関連付けられていることを覚えておいてください)...おそらくorderdictまたはさらにdictだけが必要です...
gruszczy
sの答えを拡張するために、次のようなパターンを使用しました。
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
がより適切でしょう。
次のこともできます。
locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
setattr(self, arg, locs[arg])
もちろん、inspect
モジュールをインポートする必要があります。
これは、追加のインポートなしのソリューションです。
小さなヘルパー関数により、より便利で再利用可能になります。
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)
これを処理する(そして他の多くの定型文を回避する)興味深いライブラリは attrs です。たとえば、あなたの例はこれに減らすことができます(クラスがMyClass
と呼ばれると仮定します):
import attr
@attr.s
class MyClass:
x = attr.ib()
y = attr.ib()
z = attr.ib()
__init__
メソッドは、他のこともしない限り、もう必要ありません。 Glyph Lefkowitzによる素敵な紹介 です。
私の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)
そして、__init__
からauto_init(locals())
を呼び出すだけです
Pythonで物事を行うのは自然な方法です。もっと賢いものを発明しようとしないでください。あなたのチームの誰もが理解できないような過度に賢いコードにつながります。チームプレーヤーになりたい場合は、この方法で書き続けてください。
Python 3.7では、dataclass
モジュールから利用可能な dataclasses
デコレーターを(ab)使用できます。ドキュメントから:
このモジュールは、
__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__()
を追加します。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のリリース日に書いているので、使用パターンはまだ十分に確立されていません。