私は長い中空の「データ」クラスを名前付きタプルに変換しようとしています。私のクラスは現在次のようになっています。
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)
しかし、これはリファクタリングされた名前付きTupleの場合には機能しません。すべてのフィールドを渡すことが期待されるためです。もちろん、Node(val)
の出現をNode(val, None, None)
に置き換えることもできますが、それは私の好みではありません。
それで、多くのコードの複雑さ(メタプログラミング)を追加せずに再書き込みを成功させる良いトリックがありますか、それともピルを飲み込んで「検索と置換」を進める必要がありますか? :)
defaultsパラメーターを使用します。
>>> from collections import namedtuple
>>> fields = ('val', 'left', 'right')
>>> Node = namedtuple('Node', fields, defaults=(None,) * len(fields))
>>> Node()
Node(val=None, left=None, right=None)
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)
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)
ラッパーは次のとおりです。これにより、(オプションで)デフォルト値を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)
これは ドキュメントからの直接の例 :
_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")
しかし、ここで与えられた他の回答のいくつかがより気に入っています。完全を期すためにこれを追加したかっただけです。
組み込みの名前付きタプルだけで簡単な方法があるかどうかはわかりません。この機能を持つ recordtype というNiceモジュールがあります。
>>> 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)
Python3.7 +には、新しい defaults = キーワード引数があります。
defaultsは、
None
または反復可能なデフォルト値です。デフォルト値のあるフィールドは、デフォルトのないフィールドの後に来る必要があるため、defaultsが右端のパラメーターに適用されます。たとえば、フィールド名が['x', 'y', 'z']
で、デフォルトが(1, 2)
の場合、x
は必須の引数になり、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)
短く、シンプルで、人々が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))
all引数が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)
これも使用できます:
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)
Python 3.7:namedtuple定義へのdefaults
paramの導入。
ドキュメントに示されている例:
>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._fields_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)
続きを読む こちら 。
@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'
このバージョンは読みやすいと思います:
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回作成する必要があるほど効率的ではありませんが、モジュール内でデフォルトの重複を定義し、関数に置換行を実行させるだけで変更できます。
namedtuple
をデータクラスとして使用しているため、python 3.7はこの目的のために@dataclass
デコレーターを導入することに注意する必要があります。もちろん、デフォルト値があります。
ドキュメントの例 :
@dataclass
class C:
a: int # 'a' has no default value
b: int = 0 # assign a default value for 'b'
namedtuple
をハッキングするよりもはるかにクリーンで読みやすく、使用可能です。 namedtuple
sの使用が3.7の採用により低下すると予測することは難しくありません。
この答え 別の質問に触発され、ここに metaclass に基づいて、 super
(将来のサブカルシングを正しく処理するために)を使用して提案されたソリューションがあります。 justinfay's answer とよく似ています。
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)))
Recordtypeを使用するjterraceの答えは素晴らしいですが、ライブラリの作成者は namedlist プロジェクトを使用することを推奨しています。このプロジェクトでは、可変(namedlist
)と不変(namedtuple
)実装。
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)
別の解決策:
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)
デフォルトの引数を持つ名前付きタプルのニース構文を使用した、簡潔で一般的な回答を次に示します。
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
Advanced Enum (aenum)
ライブラリのNamedTuple
クラスを使用し、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
ベースであることと同様に、シンプルです。