364

長い中空の「データ」クラスを名前付きタプルに変換しようとしています。私のクラスは現在次のようになっています。

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')

しかし、ここには問題があります。私の元のクラスでは、値だけを渡すことができ、named/keyword引数にデフォルト値を使用してデフォルトを処理していました。何かのようなもの:

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

しかし、これは、リファクタリングされた名前付きタプルの場合、すべてのフィールドを渡すことを想定しているため、機能しません。Node(val)もちろん、 toの出現を置き換えることはできますNode(val, None, None)が、それは私の好みではありません。

それで、コードの複雑さ(メタプログラミング)をあまり追加せずに書き直しを成功させることができる良いトリックがありますか、それともピルを飲み込んで「検索と置換」を続行する必要がありますか?:)

4

22 に答える 22

658

Python 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)

Python3.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)

Python2.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)

Python2.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)
于 2013-08-21T02:40:32.540 に答える
151

私は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)

これにより、直感的な型階層が保持されますが、クラスを装ったファクトリ関数の作成では保持されません。

于 2013-05-23T18:10:31.807 に答える
102

関数でラップします。

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

def Node(val, left=None, right=None):
  return NodeT(val, left, right)
于 2012-07-05T20:10:15.687 に答える
90

Python 3.6.1以降でtyping.NamedTupleは、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でのアノテーション:パラメーターと変数の場合は後、関数の場合は後の式 )の現在の仕様の1つの癖は、->定義時に評価されることです*。したがって、「クラスの本体全体が実行されるとクラス名が定義される」'Node'ため、NameErrorを回避するために、上記のクラスフィールドのアノテーションは文字列である必要があります。

この種のタイプヒントは「前方参照」([1][2])と呼ばれ、PEP 563では、 Python 3.7+に__future__前方参照を使用できるインポート(4.0ではデフォルトで有効になります)があります。引用符なしで、評価を延期します。

* AFAICTのみのローカル変数注釈は、実行時に評価されません。(出典:PEP 526

于 2017-04-01T13:58:10.770 に答える
21

これはドキュメントからの直接の例です:

デフォルト値は、_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")

しかし、私はここで与えられた他の答えのいくつかがより好きです。完全を期すためにこれを追加したかっただけです。

于 2015-04-20T18:53:55.230 に答える
19

組み込みの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)
于 2012-07-05T19:25:29.397 に答える
15

justinfayの答えに触発されたよりコンパクトなバージョンは次のとおりです。

from collections import namedtuple
from functools import partial

Node = namedtuple('Node', ('val left right'))
Node.__new__ = partial(Node.__new__, left=None, right=None)
于 2014-01-22T18:23:13.590 に答える
15

python3.7 +には、まったく新しいdefaults=キーワード引数があります。

デフォルトNone、デフォルト値またはデフォルト値の反復可能です。デフォルト値のあるフィールドは、デフォルトのないフィールドの後に来る必要があるため、デフォルトは右端のパラメーターに適用されます。たとえば、フィールド名が['x', 'y', 'z']であり、デフォルトがである場合(1, 2)、thenxは必須の引数になりy、デフォルトは1になり、デフォルトはにzなります2

使用例:

$ ./python
Python 3.7.0b1+ (heads/3.7:4d65430, Feb  1 2018, 09:28:35) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from collections import namedtuple
>>> nt = namedtuple('nt', ('a', 'b', 'c'), defaults=(1, 2))
>>> nt(0)
nt(a=0, b=1, c=2)
>>> nt(0, 3)  
nt(a=0, b=3, c=2)
>>> nt(0, c=3)
nt(a=0, b=1, c=3)
于 2018-02-01T17:36:06.067 に答える
7

isinstance短く、シンプルで、人々が不適切に使用することはありません。

class Node(namedtuple('Node', ('val', 'left', 'right'))):
    @classmethod
    def make(cls, val, left=None, right=None):
        return cls(val, left, right)

# Example
x = Node.make(3)
x._replace(right=Node.make(4))
于 2016-07-12T15:07:10.747 に答える
6

Python 3.7:defaultsnamedtuple定義でのparamの導入。

ドキュメントに示されている例:

>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._fields_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)

詳細はこちらをご覧ください

于 2018-07-26T11:17:21.547 に答える
5

欠落しているすべての引数をNone:で初期化するためのわずかに拡張された例

from collections import namedtuple

class Node(namedtuple('Node', ['value', 'left', 'right'])):
    __slots__ = ()
    def __new__(cls, *args, **kwargs):
        # initialize missing kwargs with None
        all_kwargs = {key: kwargs.get(key) for key in cls._fields}
        return super(Node, cls).__new__(cls, *args, **all_kwargs)
于 2013-09-19T09:18:08.200 に答える
4

これを使用することもできます:

import inspect

def namedtuple_with_defaults(type, default_value=None, **kwargs):
    args_list = inspect.getargspec(type.__new__).args[1:]
    params = dict([(x, default_value) for x in args_list])
    params.update(kwargs)

    return type(**params)

これにより、基本的に、デフォルト値を使用して名前付きタプルを作成し、必要なパラメーターのみをオーバーライドすることができます。次に例を示します。

import collections

Point = collections.namedtuple("Point", ["x", "y"])
namedtuple_with_defaults(Point)
>>> Point(x=None, y=None)

namedtuple_with_defaults(Point, x=1)
>>> Point(x=1, y=None)
于 2012-10-17T17:34:08.873 に答える
4

@Denisと@Markのアプローチを組み合わせる:

from collections import namedtuple
import inspect

class Node(namedtuple('Node', 'left right val')):
    __slots__ = ()
    def __new__(cls, *args, **kwargs):
        args_list = inspect.getargspec(super(Node, cls).__new__).args[len(args)+1:]
        params = {key: kwargs.get(key) for key in args_list + kwargs.keys()}
        return super(Node, cls).__new__(cls, *args, **params) 

これは、位置引数と大文字と小文字が混在するタプルの作成をサポートする必要があります。テストケース:

>>> print Node()
Node(left=None, right=None, val=None)

>>> print Node(1,2,3)
Node(left=1, right=2, val=3)

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

>>> print Node(1, right=2, val=100)
Node(left=1, right=2, val=100)

>>> print Node(left=1, right=2, val=100)
Node(left=1, right=2, val=100)

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

TypeErrorもサポートします:

>>> Node(1, left=2)
TypeError: __new__() got multiple values for keyword argument 'left'
于 2014-08-26T08:45:45.223 に答える
4

このバージョンは読みやすいと思います。

from collections import namedtuple

def my_tuple(**kwargs):
    defaults = {
        'a': 2.0,
        'b': True,
        'c': "hello",
    }
    default_tuple = namedtuple('MY_TUPLE', ' '.join(defaults.keys()))(*defaults.values())
    return default_tuple._replace(**kwargs)

これは、オブジェクトを2回作成する必要があるほど効率的ではありませんが、モジュール内でデフォルトの重複を定義し、関数に置換行を実行させるだけで、これを変更できます。

于 2015-01-06T22:31:18.760 に答える
4

データクラスとして使用しているので、Python 3.7はまさにこの目的のためにデコレータをnamedtuple導入することに注意する必要があります。もちろん、デフォルト値があります。@dataclass

ドキュメントからの例

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

ハッキングよりもはるかにクリーンで、読みやすく、使いやすいnamedtuplenamedtuple3.7の採用により、sの使用量が減少することを予測するのは難しいことではありません。

于 2018-05-24T09:18:04.570 に答える
2

別の質問に対するこの回答に触発されて、メタクラスに基づいてsuper(将来のサブカルシングを正しく処理するために)使用する私の提案されたソリューションがあります。それはジャスティンファイの答えに非常に似ています。

from collections import namedtuple

NodeTuple = namedtuple("NodeTuple", ("val", "left", "right"))

class NodeMeta(type):
    def __call__(cls, val, left=None, right=None):
        return super(NodeMeta, cls).__call__(val, left, right)

class Node(NodeTuple, metaclass=NodeMeta):
    __slots__ = ()

それで:

>>> Node(1, Node(2, Node(4)),(Node(3, None, Node(5))))
Node(val=1, left=Node(val=2, left=Node(val=4, left=None, right=None), right=None), right=Node(val=3, left=None, right=Node(val=5, left=None, right=None)))
于 2018-02-24T17:31:23.287 に答える
2

レコードタイプを使用するというjterraceの答えは素晴らしいですが、ライブラリの作成者は、可変( )と不変( )の両方の実装を提供するnamedlistプロジェクトを使用することをお勧めします。namedlistnamedtuple

from namedlist import namedtuple
>>> Node = namedtuple('Node', ['val', ('left', None), ('right', None)])
>>> Node(3)
Node(val=3, left=None, right=None)
>>> Node(3, 'L')
Node(val=3, left=L, right=None)
于 2018-10-05T11:21:24.887 に答える
1

これは、デフォルトの引数を持つ名前付きタプルの優れた構文を使用した、短くて単純な一般的な回答です。

import collections

def dnamedtuple(typename, field_names, **defaults):
    fields = sorted(field_names.split(), key=lambda x: x in defaults)
    T = collections.namedtuple(typename, ' '.join(fields))
    T.__new__.__defaults__ = tuple(defaults[field] for field in fields[-len(defaults):])
    return T

使用法:

Test = dnamedtuple('Test', 'one two three', two=2)
Test(1, 3)  # Test(one=1, three=3, two=2)

縮小:

def dnamedtuple(tp, fs, **df):
    fs = sorted(fs.split(), key=df.__contains__)
    T = collections.namedtuple(tp, ' '.join(fs))
    T.__new__.__defaults__ = tuple(df[i] for i in fs[-len(df):])
    return T
于 2018-07-17T18:04:05.803 に答える
0

私のライブラリのNamedTupleクラスを使用し、構文を使用すると、これは非常に簡単です。Advanced Enum (aenum)class

from aenum import NamedTuple

class Node(NamedTuple):
    val = 0
    left = 1, 'previous Node', None
    right = 2, 'next Node', None

潜在的な欠点の1つは__doc__、デフォルト値を持つ属性の文字列が必要になることです(単純な属性の場合はオプションです)。使用中は次のようになります。

>>> Node()
Traceback (most recent call last):
  ...
TypeError: values not provided for field(s): val

>>> Node(3)
Node(val=3, left=None, right=None)

これが持つ利点justinfay's answer

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)

シンプルであるだけでmetaclassなく、ベースではなくベースになっexecています。

于 2016-11-30T15:02:17.197 に答える
0

別の解決策:

import collections


def defaultargs(func, defaults):
    def wrapper(*args, **kwargs):
        for key, value in (x for x in defaults[len(args):] if len(x) == 2):
            kwargs.setdefault(key, value)
        return func(*args, **kwargs)
    return wrapper


def namedtuple(name, fields):
    NamedTuple = collections.namedtuple(name, [x[0] for x in fields])
    NamedTuple.__new__ = defaultargs(NamedTuple.__new__, [(NamedTuple,)] + fields)
    return NamedTuple

使用法:

>>> Node = namedtuple('Node', [
...     ('val',),
...     ('left', None),
...     ('right', None),
... ])
__main__.Node

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

>>> Node(1, 2, right=3)
Node(val=1, left=2, right=3)
于 2017-03-27T11:06:20.967 に答える
0

タイプアノテーションを使用する可能性を維持したい場合は、残念ながら@ mark-lodatoによる非常に優れたソリューションは使用できません(設定では失敗します__defaults__)。別の方法はattrsを使用することです:

import attr

 
@attr.s
class Node(object):
    val: str = attr.ib()
    left: 'Node' = attr.ib(None)
    right: 'Node' = attr.ib(None)

これは持っています:

  • タイプ注釈
  • 素敵__str____repr__
  • 本当のクラスなのでカスタマイズ可能
  • すべてのPythonバージョンで同じ実装
于 2021-04-05T06:55:51.680 に答える
-1

これは柔軟性は劣りますが、MarkLodatoのラッパーのより簡潔なバージョンです。フィールドとデフォルトを辞書として使用します。

import collections
def namedtuple_with_defaults(typename, fields_dict):
    T = collections.namedtuple(typename, ' '.join(fields_dict.keys()))
    T.__new__.__defaults__ = tuple(fields_dict.values())
    return T

例:

In[1]: fields = {'val': 1, 'left': 2, 'right':3}

In[2]: Node = namedtuple_with_defaults('Node', fields)

In[3]: Node()
Out[3]: Node(val=1, left=2, right=3)

In[4]: Node(4,5,6)
Out[4]: Node(val=4, left=5, right=6)

In[5]: Node(val=10)
Out[5]: Node(val=10, left=2, right=3)
于 2016-01-19T14:20:49.503 に答える