“self.x = x; self.y = y; __init__의 self.z = z”패턴?
나는 같은 패턴을 본다
def __init__(self, x, y, z):
...
self.x = x
self.y = y
self.z = z
...
더 자주, 더 많은 매개 변수가있는 경우가 많습니다. 이러한 유형의 지루한 반복성을 피할 수있는 좋은 방법이 있습니까? namedtuple
대신 클래스를 상속해야합니까 ?
편집 : 파이썬 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__))
면책 조항 : 여러 사람들 이이 솔루션을 제시하는 데 관심이있는 것 같습니다. 그래서 나는 매우 명확한 면책 조항을 제공 할 것입니다. 이 솔루션을 사용하지 않아야합니다. 나는 정보로만 제공하므로 언어가 가능하다는 것을 알고 있습니다. 나머지 답변은 언어 능력을 보여주는 것이지, 이런 식으로 언어를 사용하는 것을 보증하지는 않습니다.
There isn't really anything wrong with explicitly copying parameters into attributes. If you have too many parameters in the ctor, it is sometimes considered a code smell and maybe you should group these params into a fewer objects. Other times, it is necessary and there is nothing wrong with it. Anyway, doing it explicitly is the way to go.
However, since you are asking HOW it can be done (and not whether it should be done), then one solution is this:
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
As others have mentioned, the repetition isn't bad, but in some cases a namedtuple can be a great fit for this type of issue. This avoids using locals() or kwargs, which are usually a bad idea.
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
I've found limited use for it, but you can inherit a namedtuple as with any other object (example continued):
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()
explicit is better than implicit ... so sure you could make it more concise:
def __init__(self,a,b,c):
for k,v in locals().items():
if k != "self":
setattr(self,k,v)
The better question is should you?
... that said if you want a named tuple I would recommend using a namedtuple (remember tuples have certain conditions attached to them) ... perhaps you want an ordereddict or even just a dict ...
To expand on gruszczy
s answer, I have used a pattern like:
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))
I like this method because it:
- avoids repetition
- is resistant against typos when constructing an object
- works well with subclassing (can just
super().__init(...)
) - allows for documentation of the attributes on a class-level (where they belong) rather than in
X.__init__
Prior to Python 3.6, this gives no control over the order in which the attributes are set, which could be a problem if some attributes are properties with setters that access other attributes.
It could probably be improved upon a bit, but I'm the only user of my own code so I am not worried about any form of input sanitation. Perhaps an AttributeError
would be more appropriate.
You could also do:
locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
setattr(self, arg, locs[arg])
Of course, you would have to import the inspect
module.
This is a solution without any additional imports.
Helper function
A small helper function makes it more convenient and re-usable:
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)
Application
You need to call it with locals()
:
class A:
def __init__(self, x, y, z):
auto_init(locals())
Test
a = A(1, 2, 3)
print(a.__dict__)
Output:
{'y': 2, 'z': 3, 'x': 1}
Without changing locals()
If you don't like to change locals()
use this version:
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)
An interesting library that handles this (and avoids a lot of other boilerplate) is attrs. Your example, for instance, could be reduced to this (assume the class is called MyClass
):
import attr
@attr.s
class MyClass:
x = attr.ib()
y = attr.ib()
z = attr.ib()
You don't even need an __init__
method anymore, unless it does other stuff as well. Here's a nice introduction by Glyph Lefkowitz.
My 0.02$. It is very close to Joran Beasley answer, but more elegant:
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)
Additionally, Mike Müller's answer (the best one to my taste) can be reduced with this technique:
def auto_init(ns):
self = ns.pop('self')
vars(self).update(ns)
And the just call auto_init(locals())
from your __init__
It's a natural way to do things in Python. Don't try to invent something more clever, it will lead to overly clever code that no one on your team will understand. If you want to be a team player and then keep writing it this way.
Python 3.7 onwards
In Python 3.7, you may (ab)use the dataclass
decorator, available from the dataclasses
module. From the documentation:
This module provides a decorator and functions for automatically adding generated special methods such as
__init__()
and__repr__()
to user-defined classes. It was originally described in PEP 557.The member variables to use in these generated methods are defined using PEP 526 type annotations. For example this code:
@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
Will add, among other things, a
__init__()
that looks like: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
Note that this method is automatically added to the class: it is not directly specified in the InventoryItem definition shown above.
If your class is large and complex, it may be inappropriate to use a dataclass
. I'm writing this on the day of release of Python 3.7.0, so usage patterns are not yet well established.
'IT story' 카테고리의 다른 글
Bash에서 배열을 슬라이스하는 방법 (0) | 2020.05.26 |
---|---|
package.json에서 필요한 Node.js 버전을 지정하려면 어떻게해야합니까? (0) | 2020.05.26 |
Java ClassLoader 란 무엇입니까? (0) | 2020.05.26 |
PHP에서 GET URL 매개 변수 (0) | 2020.05.26 |
모든 (가벼운) 태그 생성 날짜를 표시하는 git 명령 (0) | 2020.05.26 |