“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
모든 수용력이 나쁜 것은 아닙니다. 파이썬의 클래스 모델에는 인스턴스 속성에 대한 명시적인 정의가 포함되어 있지 않으므로 이러한 할당은 자체 문서화에 해당합니다.
chepner

4
@chepner : 음, 명백한 정의 가 필요 하지 않습니다 . __slots__그래도 목적으로 사용할 수 있습니다 . 그것은 온화한 비현실적입니다 (메모리 절약을 위해 더 장황합니다).하지만 이름을 입력하면 완전히 새로운 속성을 자동으로 활성화 할 위험을 피하는 것이 좋습니다.
ShadowRanger

2
좋은 에디터라면 템플릿이있을 것입니다. 입력 ini <shortcut> x, y, z): <shortcut>하면 완료됩니다.
Gerenuk

3
Namedtuples는 굉장 경우 당신은 불변의 가치 개체를합니다. 규칙적이고 변경 가능한 클래스를 원하면 사용할 수 없습니다.
RemcoGerlich

4
"안함"은 좋은 옵션입니다. 사용 가능한 옵션은 메소드 서명 (및 잠재적으로 전체 인터페이스)을 강제 종료합니다. 또한 클래스에 초기화 할 수없는 필드가있는 경우 클래스를 분할하는 것이 좋습니다.
Kroltan

답변:


87

편집 : 파이썬 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
좋은 대답이지만 signature
python2.7

3
@alexis "decorator.decorator"데코레이터는 자동으로 기능을 감 쌉니다
Siphor

4
나는 이것을 사랑할지 싫어할지에 대해 상당히 찢어졌습니다. 서명을 보존 해 주셔서 감사합니다.
Kyle Strand

14
"... 명시 적은 암시적인 것보다 낫다. 단순한 것이 복잡한 것보다 낫다. ..."
Jack Stout

9
-1 솔직히 이것은 끔찍합니다. 이 코드가 한 눈에 무엇을하고 있는지 전혀 알 수 없으며 문자 그대로 코드 양의 10 배입니다. 영리하다는 것은 시원하게 느껴지지만 이것은 명백한 영리함의 오용입니다.
Ian Newson 2019

108

면책 조항 : 여러 사람들 이이 솔루션을 제시하는 데 관심이있는 것 같습니다. 그래서 나는 매우 명확한 면책 조항을 제공 할 것입니다. 이 솔루션을 사용하지 않아야합니다. 나는 정보로만 제공하므로 언어가 가능하다는 것을 알고 있습니다. 나머지 답변은 언어 기능을 보여주는 것이지, 이런 식으로 언어 기능을 사용하는 것을 보증하지는 않습니다.


매개 변수를 속성에 명시 적으로 복사하는 데 실제로 아무런 문제가 없습니다. ctor에 매개 변수가 너무 많으면 코드 냄새로 간주되어 이러한 매개 변수를 더 적은 수의 개체로 그룹화해야 할 수도 있습니다. 다른 경우에는 필요하며 아무런 문제가 없습니다. 어쨌든, 명시 적으로 수행하는 것이 좋습니다.

그러나 수행 할 수있는 방법을 묻는 것이기 때문에 하나의 해결책은 다음과 같습니다.

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)조금 더 파이썬적일 수도 있지만
Joran Beasley

44
이 방법의 문제점은 A.__init__실제로 예상 되는 인수에 대한 기록이없고 잘못 입력 된 인수 이름에 대한 오류 검사가 없다는 것입니다.
MaxB

7
@JoranBeasley 인스턴스 딕셔너리를 맹목적으로 업데이트하면 kwargsSQL 인젝션 공격과 동일한 상태가됩니다. 객체에 이름이 지정된 메소드 가 있고 생성자에 my_method이름이 지정된 인수를 전달 하면 사전이 방금 메소드를 겹쳐 씁니다. my_methodupdate()
Pedro

3
다른 사람들이 말했듯이 제안은 프로그래밍 스타일이 실제로 좋지 않습니다. 중요한 정보를 숨 깁니다. 이를 표시 할 수 있지만 OP를 사용하지 말 것을 명시 적으로 권장해야합니다.
Gerenuk

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메소드를 그냥 작성할 수 있으며 return tuple(self), 나중에 더 많은 필드가 명명 된 튜플 정의에 추가되면 더 유지 관리가 가능합니다.
PaulMcG

1
또한 namedtuple 선언 문자열은 필드 이름 사이에 쉼표가 필요하지 않으며 XYZ = namedtuple("XYZ", "x y z")작동합니다.
PaulMcG

@PaulMcGuire에게 감사드립니다. 상속과 그 간격을 보여주기 위해 정말 간단한 추가 기능을 생각하려고했습니다. 당신은 100 % 옳고 다른 상속 된 객체들과도 잘 어울립니다! 필드 이름은 쉼표 또는 공백으로 구분할 수 있습니다. CSV를 습관보다 선호합니다.
소형 쉘 스크립트

1
나는 종종 namedtuple이 정확한 목적을 위해 s를 사용합니다 . 특히 함수가 고도로 매개 변수화되고 함께 이해되는 수많은 계수가있는 수학적 코드에서.
detly

문제 namedtuple는 읽기 전용이라는 것입니다. 당신은 그렇게 할 수 없습니다 abc.x += 1.
hamstergene

29

명시 적이 암시 적보다 낫습니다. 따라서보다 간결하게 만들 수 있습니다.

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

더 좋은 질문은 당신이해야합니까?

... 명명 된 튜플을 원한다면 명명 된 튜플을 사용하는 것이 좋습니다 (튜플에는 특정 조건이 첨부되어 있음을 기억하십시오) ...


그러면 객체 자체가 속성이기 때문에 주기적 가비지 콜렉션이 필요합니다
John La Rooy

3
@bernie (또는 bemie?), 때때로 키닝이 어렵다
고양이

4
약간 더 효율적인 테스트 if k != "self":를 위해 if v is not self:문자열 비교가 아닌 저렴한 신원 테스트 로 변경 될 수 있습니다 . 나는 기술적으로 __init__건설 후 두 번째로 호출 될 수 있다고 가정 self하고 후속 주장으로 통과 시켰지만 실제로는 어떤 종류의 괴물이 그렇게 할 것이라고 생각하고 싶지 않습니다. :-)
ShadowRanger

이를 반환 값으로하는 함수로 만들 수 있습니다 locals: set_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__.
Mike Müller

마우스 오른쪽 단추로, 독자는 지역 변수에 영향을 미칠 것으로 예상하지만, 수도 있고 수도 없는 , 상황의 수에 따라. 요점은 그것이 UB라는 것입니다.
MaxB

인용구 : "이 사전의 내용을 수정해서는 안됩니다"
MaxB

@MaxB locals ()를 변경하지 않는 버전을 추가했습니다.
Mike Müller

7

이것을 처리하고 많은 다른 상용구를 피하는 흥미로운 라이브러리는 attrs 입니다. 예를 들어, 예를 다음과 같이 줄일 수 있습니다 (클래스가라고 가정 MyClass).

import attr

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

__init__다른 일을하지 않는 한 더 이상 방법이 필요하지 않습니다 . 여기 문양 레프코위츠하여 좋은 소개 .


의 기능은 어느 정도 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)

또한이 기법으로 Mike Mü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

파이썬에서 일을하는 자연스러운 방법입니다. 더 영리한 것을 발명하려고 시도하지 마십시오. 팀원 중 누구도 이해할 수없는 지나치게 영리한 코드로 이어질 것입니다. 당신이 팀 플레이어가되고 싶다면 다음과 같이 작성하십시오.


4

파이썬 3.7 이상

Python 3.7에서는 모듈 dataclass에서 사용 가능한 데코레이터를 사용할 수 있습니다 dataclasses. 설명서에서 :

이 모듈은 사용자 정의 클래스 __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 출시일에 이것을 작성하고 있으므로 사용 패턴이 아직 잘 설정되지 않았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.