[python] 선택적 키워드 인수에 대한 명명 된 튜플 및 기본값

긴 속이 빈 “데이터”클래스를 명명 된 튜플로 변환하려고합니다. 내 수업은 현재 다음과 같습니다.

class Node(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

변환 후 namedtuple다음과 같이 보입니다.

from collections import namedtuple
Node = namedtuple('Node', 'val left right')

그러나 여기에 문제가 있습니다. 내 원래 클래스를 사용하면 값만 전달할 수 있었고 명명 된 / 키워드 인수에 기본값을 사용하여 기본값을 처리했습니다. 다음과 같은 것 :

class BinaryTree(object):
    def __init__(self, val):
        self.root = Node(val)

그러나 이것은 내가 모든 필드를 전달할 것으로 예상하기 때문에 리팩토링 된 명명 된 튜플의 경우에는 작동하지 않습니다. 물론 Node(val)to 의 발생을 바꿀 수 Node(val, None, None)있지만 내 취향에 맞지 않습니다.

그렇다면 많은 코드 복잡성 (메타 프로그래밍)을 추가하지 않고도 재 작성을 성공적으로 수행 할 수있는 좋은 트릭이 있습니까? 아니면 약을 삼키고 “검색 및 교체”를 계속해야합니까? 🙂



답변

파이썬 3.7

기본값 매개 변수를 사용하십시오 .

>>> from collections import namedtuple
>>> fields = ('val', 'left', 'right')
>>> Node = namedtuple('Node', fields, defaults=(None,) * len(fields))
>>> Node()
Node(val=None, left=None, right=None)

또는 더 좋은 방법은 namedtuple보다 훨씬 좋은 새로운 데이터 클래스 라이브러리를 사용하는 것입니다.

>>> from dataclasses import dataclass
>>> from typing import Any
>>> @dataclass
... class Node:
...     val: Any = None
...     left: 'Node' = None
...     right: 'Node' = None
>>> Node()
Node(val=None, left=None, right=None)

Python 3.7 이전

설정 Node.__new__.__defaults__기본값으로.

>>> from collections import namedtuple
>>> Node = namedtuple('Node', 'val left right')
>>> Node.__new__.__defaults__ = (None,) * len(Node._fields)
>>> Node()
Node(val=None, left=None, right=None)

Python 2.6 이전

설정 Node.__new__.func_defaults기본값으로.

>>> from collections import namedtuple
>>> Node = namedtuple('Node', 'val left right')
>>> Node.__new__.func_defaults = (None,) * len(Node._fields)
>>> Node()
Node(val=None, left=None, right=None)

주문

모든 버전의 Python에서 namedtuple에있는 것보다 적은 수의 기본값을 설정하면 가장 오른쪽 매개 변수에 기본값이 적용됩니다. 이를 통해 일부 인수를 필수 인수로 유지할 수 있습니다.

>>> Node.__new__.__defaults__ = (1,2)
>>> Node()
Traceback (most recent call last):
  ...
TypeError: __new__() missing 1 required positional argument: 'val'
>>> Node(3)
Node(val=3, left=1, right=2)

Python 2.6 ~ 3.6 용 래퍼

여기에 당신을위한 래퍼가 있습니다. 이것은 당신이 (선택적으로) 기본값을 None. 이것은 필수 인수를 지원하지 않습니다.

import collections
def namedtuple_with_defaults(typename, field_names, default_values=()):
    T = collections.namedtuple(typename, field_names)
    T.__new__.__defaults__ = (None,) * len(T._fields)
    if isinstance(default_values, collections.Mapping):
        prototype = T(**default_values)
    else:
        prototype = T(*default_values)
    T.__new__.__defaults__ = tuple(prototype)
    return T

예:

>>> Node = namedtuple_with_defaults('Node', 'val left right')
>>> Node()
Node(val=None, left=None, right=None)
>>> Node = namedtuple_with_defaults('Node', 'val left right', [1, 2, 3])
>>> Node()
Node(val=1, left=2, right=3)
>>> Node = namedtuple_with_defaults('Node', 'val left right', {'right':7})
>>> Node()
Node(val=None, left=None, right=7)
>>> Node(4)
Node(val=4, left=None, right=7)


답변

namedtuple을 서브 클래 싱하고 __new__메서드를 재정의했습니다 .

from collections import namedtuple

class Node(namedtuple('Node', ['value', 'left', 'right'])):
    __slots__ = ()
    def __new__(cls, value, left=None, right=None):
        return super(Node, cls).__new__(cls, value, left, right)

이것은 클래스로 위장한 팩토리 함수의 생성이 수행하지 않는 직관적 인 유형 계층을 보존합니다.


답변

함수로 감싸십시오.

NodeT = namedtuple('Node', 'val left right')

def Node(val, left=None, right=None):
  return NodeT(val, left, right)


답변

함께 typing.NamedTuple파이썬에서 3.6.1+ 당신은 기본 값과 NamedTuple 필드 유형 약어를 모두 제공 할 수 있습니다. typing.Any전자 만 필요한 경우 사용 :

from typing import Any, NamedTuple


class Node(NamedTuple):
    val: Any
    left: 'Node' = None
    right: 'Node' = None

용법:

>>> Node(1)
Node(val=1, left=None, right=None)
>>> n = Node(1)
>>> Node(2, left=n)
Node(val=2, left=Node(val=1, left=None, right=None), right=None)

또한 기본값과 선택적 변경이 모두 필요한 경우 Python 3.7은 일부 (많은?) 경우에 명명 된 튜플을 대체 할 수 있는 데이터 클래스 (PEP 557) 를 가질 것입니다.


참고 : Python에서 주석 ( :매개 변수 및 변수의 경우 뒤에 표현식 ->, 함수의 경우 뒤에 표현식) 의 현재 사양의 한 가지 단점은 정의 시간 *에 평가된다는 것 입니다. 따라서 “클래스의 전체 본문이 실행되면 클래스 이름이 정의되기 때문에” 'Node'위의 클래스 필드에 대한 주석은 NameError를 피하기 위해 문자열이어야합니다.

이런 종류의 유형 힌트를 “순방향 참조”( [1] , [2] )라고하며 PEP 563을 사용하면 Python 3.7+는 __future__순방향 참조를 사용할 수 있는 가져 오기 (기본적으로 4.0에서 활성화 됨)를 갖게됩니다. 따옴표없이 평가를 연기합니다.

* AFAICT 전용 로컬 변수 주석은 런타임에 평가되지 않습니다. (출처 : PEP 526 )


답변

다음은 문서에서 바로 나온 예입니다 .

_replace ()를 사용하여 프로토 타입 인스턴스를 사용자 정의하여 기본값을 구현할 수 있습니다.

>>> Account = namedtuple('Account', 'owner balance transaction_count')
>>> default_account = Account('<owner name>', 0.0, 0)
>>> johns_account = default_account._replace(owner='John')
>>> janes_account = default_account._replace(owner='Jane')

따라서 OP의 예는 다음과 같습니다.

from collections import namedtuple
Node = namedtuple('Node', 'val left right')
default_node = Node(None, None, None)
example = default_node._replace(val="whut")

그러나 여기에 제공된 다른 답변 중 일부가 더 좋습니다. 완전성을 위해 이것을 추가하고 싶었습니다.


답변

내장 namedtuple만으로 쉬운 방법이 있는지 잘 모르겠습니다. 이 기능을 가진 recordtype 이라는 멋진 모듈 이 있습니다.

>>> from recordtype import recordtype
>>> Node = recordtype('Node', [('val', None), ('left', None), ('right', None)])
>>> Node(3)
Node(val=3, left=None, right=None)
>>> Node(3, 'L')
Node(val=3, left=L, right=None)


답변

다음은 justinfay의 답변에서 영감을 얻은 더 간결한 버전입니다.

from collections import namedtuple
from functools import partial

Node = namedtuple('Node', ('val left right'))
Node.__new__ = partial(Node.__new__, left=None, right=None)