Python에서 인스턴스 변수의 기본값을 어떻게 선언해야합니까?


90

클래스 멤버에게 다음과 같은 기본값을 제공해야합니까?

class Foo:
    num = 1

아니면 이것처럼?

class Foo:
    def __init__(self):
        self.num = 1

에서 이 질문에 나는, 두 경우 모두에서 그 발견

bar = Foo()
bar.num += 1

잘 정의 된 작업입니다.

첫 번째 방법은 클래스 변수를 제공하고 두 번째 방법은 제공하지 않는다는 것을 이해합니다. 그러나 클래스 변수가 필요하지 않고 인스턴스 변수에 대한 기본값 만 설정하면되는 경우 두 메서드 모두 똑같이 좋은가요? 아니면 그들 중 하나가 다른 것보다 더 '비단뱀 적'입니까?

내가 주목 한 한 가지는 Django 튜토리얼 에서 두 번째 방법을 사용하여 모델을 선언한다는 것입니다. 개인적으로 두 번째 방법이 더 우아하다고 생각하지만 '표준'방법이 무엇인지 알고 싶습니다.

답변:


132

bp의 대답을 확장하여 불변 유형이 의미하는 바를 보여주고 싶었습니다.

첫째, 이것은 괜찮습니다.

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

그러나 이것은 변경 불가능한 (변경 불가능한) 유형에 대해서만 작동합니다. 기본값이 변경 가능한 경우 (바꿀 수 있음) 대신 다음과 같은 상황이 발생합니다.

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

a와 둘 다 b공유 속성이 있습니다. 이것은 종종 원치 않는 것입니다.

유형이 변경 가능할 때 인스턴스 변수에 대한 기본값을 정의하는 Python 방식입니다.

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

내 첫 번째 코드 스 니펫이 작동하는 이유는 변경 불가능한 유형으로 Python이 원할 때마다 새 인스턴스를 생성하기 때문입니다. 1 대 1을 더해야하는 경우, 이전 1은 변경할 수 없기 때문에 Python은 새 2를 만듭니다. 그 이유는 대부분 해싱 때문이라고 생각합니다.


2
최근에 변경 가능한 속성에 대한 기본값을 구현하는 훨씬 간단한 방법을 발견했습니다. 메소드 self.attr = attr or []내에서 작성 하십시오 __init__. 동일한 결과를 얻었으며 (내 생각에) 여전히 명확하고 읽기 쉽습니다.
Bill

4
죄송합니다. 위의 의견에서 제안에 대해 더 많은 연구를 수행했으며 분명히 동일 하지 않으며 권장되지 않습니다 .
Bill

TestC 클래스에 빈 괄호가있는 이유는 무엇입니까? 이것이 파이썬 2 레거시입니까?
ChrisG

55

두 조각은 서로 다른 일을하기 때문에 취향의 문제가 아니라 상황에서 올바른 행동이 무엇인지의 문제입니다. Python 문서 는 차이점을 설명하지만 다음은 몇 가지 예입니다.

전시 A를

class Foo:
  def __init__(self):
    self.num = 1

이것은 numFoo 인스턴스에 바인딩 됩니다 . 이 필드에 대한 변경 사항은 다른 인스턴스로 전파되지 않습니다.

그러므로:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

별첨 B

class Bar:
  num = 1

이것은 numBar 클래스에 바인딩 됩니다 . 변경 사항이 전파됩니다!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

실제 답변

클래스 변수가 필요하지 않고 인스턴스 변수에 대한 기본값 만 설정하면되는 경우 두 메서드 모두 똑같이 좋은가요? 아니면 그들 중 하나가 다른 것보다 더 '비단뱀 적'입니까?

전시회 B의 코드는 이것에 대해 명백히 잘못되었습니다. 클래스 속성 (인스턴스 생성시 기본값)을 단일 인스턴스에 바인딩하려는 이유는 무엇입니까?

전시회 A의 코드는 괜찮습니다.

생성자에서 인스턴스 변수에 대한 기본값을 제공하려면 다음을 수행합니다.

class Foo:
  def __init__(self, num = None):
    self.num = num if num is not None else 1

...또는:

class Foo:
  DEFAULT_NUM = 1
  def __init__(self, num = None):
    self.num = num if num is not None else DEFAULT_NUM

... 또는 심지어 : (바람직하지만 불변 유형을 다루는 경우에만!)

class Foo:
  def __init__(self, num = 1):
    self.num = num

이렇게하면 다음을 수행 할 수 있습니다.

foo1 = Foo(4)
foo2 = Foo() #use default

1
@badp
Eliethesaiyan

첫 번째 예가 아니어야합니다def __init__(self, num = None)
PyWalker2797

6

클래스 멤버를 사용하여 기본값을 제공하는 것은 변경 불가능한 값으로 만 수행하도록주의하는 한 매우 잘 작동합니다. 목록이나 딕셔너리로하려고하면 꽤 치명적일 것입니다. 또한 기본값이 None 인 한 인스턴스 속성이 클래스에 대한 참조 인 경우에도 작동합니다.

이 기술은 Zope 위에서 실행되는 프레임 워크 인 repoze에서 매우 성공적으로 사용되는 것을 보았습니다. 여기서 장점은 클래스가 데이터베이스에 유지 될 때 기본이 아닌 속성 만 저장해야한다는 점뿐만 아니라 스키마에 새 필드를 추가해야 할 때 기존의 모든 개체가 기본값으로 새 필드를 볼 수 있다는 것입니다. 저장된 데이터를 실제로 변경할 필요가 없습니다.

좀 더 일반적인 코딩에서도 잘 작동하지만 스타일 문제입니다. 가장 행복한 것을 사용하십시오.


3

인스턴스 변수의 기본값에 대해 클래스 멤버를 사용하는 것은 좋은 생각이 아니며이 아이디어가 전혀 언급 된 것을 본 것은 처음입니다. 귀하의 예에서는 작동하지만 많은 경우에 실패 할 수 있습니다. 예를 들어 값이 변경 가능한 경우 수정되지 않은 인스턴스에서 변경하면 기본값이 변경됩니다.

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]

맨 처음 []만 복제 된 것을 제외하고 는 x.l그리고 y.l동일한 지시 대상에 연결되는 악한 이름과 다른 즉각적인 가치입니다. (언어 변호사 용어 구성)
Phlip

1

전파를 방지하는 클래스 변수를 None으로 선언 할 수도 있습니다. 이것은 잘 정의 된 클래스가 필요하고 AttributeErrors를 방지하고자 할 때 유용합니다. 예를 들면 :

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

또한 기본값이 필요한 경우 :

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

물론 __init__.


0

Python 3.7에 추가 된 기능인 dataclasses를 사용하면 클래스 인스턴스에서 기본값을 설정하는 또 다른 (매우 편리한) 방법이 있습니다. 데코레이터 dataclass는 생성자와 같은 몇 가지 메서드를 클래스에 자동으로 생성합니다. 위에 링크 된 문서에서 "[이 생성 된 메소드에서 사용할 멤버 변수는 PEP 526 유형 주석을 사용하여 정의됩니다 ."

OP의 예를 고려하면 다음과 같이 구현할 수 있습니다.

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

이 클래스 유형의 객체를 생성 할 때 선택적으로 값을 덮어 쓸 수 있습니다.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.