propias - que es*args en python
nombrados como valores predeterminados y de tupla para argumentos de palabras clave opcionales (21)
Python 3.7
Utilice el parámetro por defecto .
>>> from collections import namedtuple
>>> fields = (''val'', ''left'', ''right'')
>>> Node = namedtuple(''Node'', fields, defaults=(None,) * len(fields))
>>> Node()
Node(val=None, left=None, right=None)
Antes de Python 3.7
Establezca Node.__new__.__defaults__
en los valores predeterminados.
>>> 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)
Antes de Python 2.6
Establezca Node.__new__.func_defaults
en los valores predeterminados.
>>> 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)
Orden
En todas las versiones de Python, si establece menos valores predeterminados que los que existen en el grupo nombrado, los valores predeterminados se aplican a los parámetros más a la derecha. Esto le permite mantener algunos argumentos como argumentos requeridos.
>>> 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)
Envoltura para Python 2.6 a 3.6
Aquí hay una envoltura para usted, que incluso le permite (opcionalmente) establecer los valores predeterminados en algo distinto a None
. Esto no soporta los argumentos requeridos.
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
Ejemplo:
>>> 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)
Estoy tratando de convertir una clase de "datos" huecos en una tupla con nombre. Mi clase actualmente se ve así:
class Node(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
Después de la conversión a namedtuple
parece:
from collections import namedtuple
Node = namedtuple(''Node'', ''val left right'')
Pero hay un problema aquí. Mi clase original me permitió pasar solo un valor y se hizo cargo del valor predeterminado utilizando valores predeterminados para los argumentos nombrados / palabras clave. Algo como:
class BinaryTree(object):
def __init__(self, val):
self.root = Node(val)
Pero esto no funciona en el caso de mi tupla nombrada refactorizada, ya que espera que pase todos los campos. Por supuesto, puedo reemplazar las ocurrencias de Node(val)
a Node(val, None, None)
pero no es de mi agrado.
Entonces, ¿existe un buen truco que pueda hacer que mi reescritura sea exitosa sin agregar mucha complejidad de código (metaprogramación) o debo tragar la píldora y seguir adelante con la "búsqueda y reemplazo"? :)
Aquí hay una respuesta genérica corta y simple con una buena sintaxis para una tupla nombrada con argumentos predeterminados:
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
Uso:
Test = dnamedtuple(''Test'', ''one two three'', two=2)
Test(1, 3) # Test(one=1, three=3, two=2)
Minificado
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
Aquí hay una versión más compacta inspirada en la respuesta de justinfay:
from collections import namedtuple
from functools import partial
Node = namedtuple(''Node'', (''val left right''))
Node.__new__ = partial(Node.__new__, left=None, right=None)
Aquí hay una versión menos flexible, pero más concisa de la envoltura de Mark Lodato: toma los campos y los valores predeterminados como un diccionario.
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
Ejemplo:
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)
Combinando enfoques de @Denis y @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)
Eso debería permitir crear la tupla con argumentos posicionales y también con casos mixtos. Casos de prueba:
>>> 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)
pero también soporta TypeError:
>>> Node(1, left=2)
TypeError: __new__() got multiple values for keyword argument ''left''
Con typing.NamedTuple
en Python 3.6.1+ puede proporcionar tanto un valor predeterminado como una anotación de tipo a un campo NamedTuple. Usa la typing.Any
si solo necesitas lo primero:
from typing import Any, NamedTuple
class Node(NamedTuple):
val: Any
left: ''Node'' = None
right: ''Node'' = None
Uso:
>>> 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)
Además, en caso de que necesite tanto valores predeterminados como mutabilidad opcional, Python 3.7 tendrá clases de datos (PEP 557) que, en algunos (¿muchos?) Casos, pueden reemplazar las muestras nombradas.
Nota: una peculiaridad de la especificación actual de annotations (expresiones después:
para parámetros y variables y después ->
para funciones) en Python es que se evalúan en el momento de la definición * . Por lo tanto, dado que "los nombres de clase se definen una vez que se ha ejecutado todo el cuerpo de la clase", las anotaciones para ''Node''
en los campos de clase anteriores deben ser cadenas para evitar NameError. Este tipo de sugerencias de tipo se llama "referencia hacia adelante" ( [1] , [2] ), y con PEP 563, Python 3.7+ tendrá una importación de __future__
(que se habilitará de forma predeterminada en 4.0) que permitirá usar la función de reenvío Referencias sin comillas, aplazando su evaluación.
* Solo las anotaciones de variables locales de AFAICT no se evalúan en tiempo de ejecución. (fuente: PEP 526 )
Corto, simple, y no lleva a la gente a usar su isinstance
inapropiada:
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))
En Python3.7 + hay un nuevo valor por defaults= argumento de palabra clave.
los valores predeterminados pueden ser
None
o una iterable de los valores predeterminados. Dado que los campos con un valor predeterminado deben aparecer después de cualquier campo sin un valor predeterminado, los valores predeterminados se aplican a los parámetros más a la derecha. Por ejemplo, si los nombres de campo son[''x'', ''y'', ''z'']
y los valores predeterminados son(1, 2)
, entoncesx
será un argumento requerido,y
defecto será1
,z
defecto será2
.
Ejemplo de uso:
$ ./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)
Encuentro esta versión más fácil de leer:
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)
Esto no es tan eficiente como requiere la creación del objeto dos veces, pero puede cambiarlo definiendo la duplicación predeterminada dentro del módulo y haciendo que la función realice la línea de reemplazo.
Envuélvelo en una función.
NodeT = namedtuple(''Node'', ''val left right'')
def Node(val, left=None, right=None):
return NodeT(val, left, right)
Este es un ejemplo directamente de la documentación :
Los valores predeterminados se pueden implementar utilizando _replace () para personalizar una instancia de prototipo:
>>> 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'')
Entonces, el ejemplo del OP sería:
from collections import namedtuple
Node = namedtuple(''Node'', ''val left right'')
default_node = Node(None, None, None)
example = default_node._replace(val="whut")
Sin embargo, me gustan más algunas de las otras respuestas dadas aquí. Sólo quería añadir esto para completar.
Inspirado por esta respuesta a una pregunta diferente, aquí está mi solución propuesta basada en una metaclass y usando super
(para manejar el subcálculo futuro correctamente). Es bastante similar a share .
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__ = ()
Entonces:
>>> 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)))
La respuesta de jterrace para usar recordtype es excelente, pero el autor de la biblioteca recomienda usar su proyecto namedlist , que proporciona namedlist
tanto mutables (lista namedlist
) como inmutables (nombre 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)
No estoy seguro de si hay una manera fácil con solo el par incorporado nombrado. Hay un módulo agradable llamado recordtype que tiene esta funcionalidad:
>>> 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)
Otra solución:
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
Uso:
>>> 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)
Python 3.7: introducción de defaults
en la definición de duplicados.
Ejemplo como se muestra en la documentación:
>>> Account = namedtuple(''Account'', [''type'', ''balance''], defaults=[0])
>>> Account._fields_defaults
{''balance'': 0}
>>> Account(''premium'')
Account(type=''premium'', balance=0)
Lea más here .
Sub-asigné un nombre a tu pareja y __new__
método __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)
Esto preserva una jerarquía de tipos intuitiva, que la creación de una función de fábrica disfrazada como una clase no hace.
También puedes usar esto:
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)
Básicamente, esto le da la posibilidad de construir cualquier tupla con un valor predeterminado y anular solo los parámetros que necesita, por ejemplo:
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)
Un ejemplo ligeramente extendido para inicializar todos los argumentos que faltan con 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)
Usando la clase NamedTuple
de mi biblioteca Advanced Enum (aenum)
, y usando la sintaxis de la class
, esto es bastante simple:
from aenum import NamedTuple
class Node(NamedTuple):
val = 0
left = 1, ''previous Node'', None
right = 2, ''next Node'', None
El único inconveniente potencial es el requisito de una cadena __doc__
para cualquier atributo con un valor predeterminado (es opcional para los atributos simples). En uso parece:
>>> Node()
Traceback (most recent call last):
...
TypeError: values not provided for field(s): val
>>> Node(3)
Node(val=3, left=None, right=None)
Las ventajas que esto tiene sobre 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)
es la simplicidad, además de estar basada en metaclass
lugar de basada en exec
.
Ya que está usando namedtuple
como clase de datos, debe tener en cuenta que Python 3.7 introducirá un decorador @dataclass
para este propósito, y por supuesto tiene valores predeterminados.
Un ejemplo de la documentación :
@dataclass
class C:
a: int # ''a'' has no default value
b: int = 0 # assign a default value for ''b''
Mucho más limpio, legible y utilizable que el hacking namedtuple
. No es difícil predecir que el uso de namedtuple
s se reducirá con la adopción de 3.7.