tuplas - ¿Existencia de tupla llamada mutable en Python?
metodos de listas en python (10)
Como una alternativa muy pitónica para esta tarea, desde Python-3.7, puede usar el módulo de
dataclasses
que no solo se comporta como un
NamedTuple
mutable porque usan definiciones de clase normales sino que también admiten otras características de clases.
Desde PEP-0557:
Aunque utilizan un mecanismo muy diferente, las clases de datos pueden considerarse como "tuplas con mutable nombre con valores predeterminados". Debido a que las Clases de datos usan una sintaxis de definición de clase normal, puede usar herencia, metaclases, cadenas de documentos, métodos definidos por el usuario, fábricas de clases y otras características de clase de Python.
Se proporciona un decorador de clase que inspecciona una definición de clase para variables con anotaciones de tipo como se define en PEP 526 , "Sintaxis para anotaciones variables". En este documento, tales variables se llaman campos. Usando estos campos, el decorador agrega definiciones de métodos generados a la clase para admitir la inicialización de instancias, una repr, métodos de comparación y, opcionalmente, otros métodos como se describe en la sección Specification . Dicha clase se llama Clase de datos, pero en realidad no tiene nada de especial: el decorador agrega métodos generados a la clase y devuelve la misma clase que se le dio.
Esta característica se introduce en PEP-0557 que puede leer sobre ella en más detalles en el enlace de documentación proporcionado.
Ejemplo:
In [20]: from dataclasses import dataclass
In [21]: @dataclass
...: class InventoryItem:
...: ''''''Class for keeping track of an item in inventory.''''''
...: name: str
...: unit_price: float
...: quantity_on_hand: int = 0
...:
...: def total_cost(self) -> float:
...: return self.unit_price * self.quantity_on_hand
...:
Manifestación:
In [23]: II = InventoryItem(''bisc'', 2000)
In [24]: II
Out[24]: InventoryItem(name=''bisc'', unit_price=2000, quantity_on_hand=0)
In [25]: II.name = ''choco''
In [26]: II.name
Out[26]: ''choco''
In [27]:
In [27]: II.unit_price *= 3
In [28]: II.unit_price
Out[28]: 6000
In [29]: II
Out[29]: InventoryItem(name=''choco'', unit_price=6000, quantity_on_hand=0)
¿Alguien puede modificar namedtuple o proporcionar una clase alternativa para que funcione para objetos mutables?
Principalmente por legibilidad, me gustaría algo similar a namedtuple que haga esto:
from Camelot import namedgroup
Point = namedgroup(''Point'', [''x'', ''y''])
p = Point(0, 0)
p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
Point(x=100, y=0)
Debe ser posible encurtir el objeto resultante. Y según las características de la tupla nombrada, el orden de la salida cuando se representa debe coincidir con el orden de la lista de parámetros al construir el objeto.
Hay una alternativa mutable a
collections.namedtuple
-
recordclass
.
Tiene la misma API y huella de memoria que
namedtuple
y admite asignaciones (también debería ser más rápido).
Por ejemplo:
from recordclass import recordclass
Point = recordclass(''Point'', ''x y'')
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)
Para python 3.6 y más alto
recordclass
(desde 0.5) admite typehints:
from recordclass import recordclass, RecordClass
class Point(RecordClass):
x: int
y: int
>>> Point.__annotations__
{''x'':int, ''y'':int}
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)
Hay un example más completo (también incluye comparaciones de rendimiento).
Desde 0.9, la biblioteca
recordclass
proporciona otra variante: la función de fábrica
recordclass.structclass
.
Puede producir clases, cuyas instancias ocupan menos memoria que las instancias basadas en
__slots__
.
Esto puede ser importante para las instancias con valores de atributo, que no tiene la intención de tener ciclos de referencia.
Puede ayudar a reducir el uso de memoria si necesita crear millones de instancias.
Aquí hay un
example
ilustrativo.
Implementemos esto con la creación dinámica de tipos:
import copy
def namedgroup(typename, fieldnames):
def init(self, **kwargs):
attrs = {k: None for k in self._attrs_}
for k in kwargs:
if k in self._attrs_:
attrs[k] = kwargs[k]
else:
raise AttributeError(''Invalid Field'')
self.__dict__.update(attrs)
def getattribute(self, attr):
if attr.startswith("_") or attr in self._attrs_:
return object.__getattribute__(self, attr)
else:
raise AttributeError(''Invalid Field'')
def setattr(self, attr, value):
if attr in self._attrs_:
object.__setattr__(self, attr, value)
else:
raise AttributeError(''Invalid Field'')
def rep(self):
d = ["{}={}".format(v,self.__dict__[v]) for v in self._attrs_]
return self._typename_ + ''('' + '', ''.join(d) + '')''
def iterate(self):
for x in self._attrs_:
yield self.__dict__[x]
raise StopIteration()
def setitem(self, *args, **kwargs):
return self.__dict__.__setitem__(*args, **kwargs)
def getitem(self, *args, **kwargs):
return self.__dict__.__getitem__(*args, **kwargs)
attrs = {"__init__": init,
"__setattr__": setattr,
"__getattribute__": getattribute,
"_attrs_": copy.deepcopy(fieldnames),
"_typename_": str(typename),
"__str__": rep,
"__repr__": rep,
"__len__": lambda self: len(fieldnames),
"__iter__": iterate,
"__setitem__": setitem,
"__getitem__": getitem,
}
return type(typename, (object,), attrs)
Esto verifica los atributos para ver si son válidos antes de permitir que la operación continúe.
Entonces, ¿es esto en escabeche? Sí si (y solo si) haces lo siguiente:
>>> import pickle
>>> Point = namedgroup("Point", ["x", "y"])
>>> p = Point(x=100, y=200)
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.x
100
>>> p2.y
200
>>> id(p) != id(p2)
True
La definición debe estar en su espacio de nombres y debe existir el tiempo suficiente para que Pickle la encuentre. Entonces, si define que esto está en su paquete, debería funcionar.
Point = namedgroup("Point", ["x", "y"])
Pickle fallará si hace lo siguiente o hace que la definición sea temporal (queda fuera del alcance cuando finaliza la función, por ejemplo):
some_point = namedgroup("Point", ["x", "y"])
Y sí, conserva el orden de los campos enumerados en la creación del tipo.
La siguiente es una buena solución para Python 3: una clase mínima que usa
__slots__
y la clase base abstracta de
Sequence
;
no hace una detección de errores sofisticada o algo así, pero funciona y se comporta principalmente como una tupla mutable (a excepción de la comprobación de tipos).
from collections import Sequence
class NamedMutableSequence(Sequence):
__slots__ = ()
def __init__(self, *a, **kw):
slots = self.__slots__
for k in slots:
setattr(self, k, kw.get(k))
if a:
for k, v in zip(slots, a):
setattr(self, k, v)
def __str__(self):
clsname = self.__class__.__name__
values = '', ''.join(''%s=%r'' % (k, getattr(self, k))
for k in self.__slots__)
return ''%s(%s)'' % (clsname, values)
__repr__ = __str__
def __getitem__(self, item):
return getattr(self, self.__slots__[item])
def __setitem__(self, item, value):
return setattr(self, self.__slots__[item], value)
def __len__(self):
return len(self.__slots__)
class Point(NamedMutableSequence):
__slots__ = (''x'', ''y'')
Ejemplo:
>>> p = Point(0, 0)
>>> p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
>>> p
Point(x=100, y=0)
Si lo desea, también puede tener un método para crear la clase (aunque usar una clase explícita es más transparente):
def namedgroup(name, members):
if isinstance(members, str):
members = members.split()
members = tuple(members)
return type(name, (NamedMutableSequence,), {''__slots__'': members})
Ejemplo:
>>> Point = namedgroup(''Point'', [''x'', ''y''])
>>> Point(6, 42)
Point(x=6, y=42)
En Python 2, debe ajustarlo ligeramente; si
hereda de
Sequence
, la clase tendrá un
__dict__
y los
__slots__
dejarán de funcionar.
La solución en Python 2 es no heredar de
Sequence
, sino
object
.
Si se
isinstance(Point, Sequence) == True
, debe registrar
NamedMutableSequence
como una clase base para
Sequence
:
Sequence.register(NamedMutableSequence)
Las tuplas son, por definición, inmutables.
Sin embargo, puede hacer una subclase de diccionario donde puede acceder a los atributos con notación de puntos;
In [1]: %cpaste
Pasting code; enter ''--'' alone on the line to stop or use Ctrl-D.
:class AttrDict(dict):
:
: def __getattr__(self, name):
: return self[name]
:
: def __setattr__(self, name, value):
: self[name] = value
:--
In [2]: test = AttrDict()
In [3]: test.a = 1
In [4]: test.b = True
In [5]: test
Out[5]: {''a'': 1, ''b'': True}
Parece que la respuesta a esta pregunta es no.
A continuación está bastante cerca, pero no es técnicamente mutable.
Esto está creando una nueva instancia
namedtuple()
con un valor x actualizado:
Point = namedtuple(''Point'', [''x'', ''y''])
p = Point(0, 0)
p = p._replace(x=10)
Por otro lado, puede crear una clase simple usando
__slots__
que debería funcionar bien para actualizar frecuentemente los atributos de instancia de clase:
class Point:
__slots__ = [''x'', ''y'']
def __init__(self, x, y):
self.x = x
self.y = y
Para agregar a esta respuesta, creo que
__slots__
es un buen uso aquí porque es eficiente en memoria cuando creas muchas instancias de clase.
El único inconveniente es que no puede crear nuevos atributos de clase.
Aquí hay un hilo relevante que ilustra la eficiencia de la memoria, Diccionario vs Objeto, ¿cuál es más eficiente y por qué?
El contenido citado en la respuesta de este hilo es una explicación muy sucinta de por qué
__slots__
es más eficiente en memoria -
Slots de Python
Si desea un comportamiento similar al de namedtuples pero mutable intente namedlist
Tenga en cuenta que para ser mutable no puede ser una tupla.
Siempre que el rendimiento sea de poca importancia, uno podría usar un truco tonto como:
from collection import namedtuple
Point = namedtuple(''Point'', ''x y z'')
mutable_z = Point(1,2,[3])
types.SimpleNamespace se introdujo en Python 3.3 y admite los requisitos solicitados.
from types import SimpleNamespace
t = SimpleNamespace(foo=''bar'')
t.ham = ''spam''
print(t)
namespace(foo=''bar'', ham=''spam'')
print(t.foo)
''bar''
import pickle
with open(''/tmp/pickle'', ''wb'') as f:
pickle.dump(t, f)
La última lista
namedlist
1.7 pasa todas sus pruebas con Python 2.7 y Python 3.5 a
partir del 11 de enero de 2016.
Es una implementación pura de Python,
mientras que la
recordclass
es una extensión C.
Por supuesto, depende de sus requisitos si una extensión C es preferida o no.
Sus pruebas (pero también vea la nota a continuación):
from __future__ import print_function
import pickle
import sys
from namedlist import namedlist
Point = namedlist(''Point'', ''x y'')
p = Point(x=1, y=2)
print(''1. Mutation of field values'')
p.x *= 10
p.y += 10
print(''p: {}, {}/n''.format(p.x, p.y))
print(''2. String'')
print(''p: {}/n''.format(p))
print(''3. Representation'')
print(repr(p), ''/n'')
print(''4. Sizeof'')
print(''size of p:'', sys.getsizeof(p), ''/n'')
print(''5. Access by name of field'')
print(''p: {}, {}/n''.format(p.x, p.y))
print(''6. Access by index'')
print(''p: {}, {}/n''.format(p[0], p[1]))
print(''7. Iterative unpacking'')
x, y = p
print(''p: {}, {}/n''.format(x, y))
print(''8. Iteration'')
print(''p: {}/n''.format([v for v in p]))
print(''9. Ordered Dict'')
print(''p: {}/n''.format(p._asdict()))
print(''10. Inplace replacement (update?)'')
p._update(x=100, y=200)
print(''p: {}/n''.format(p))
print(''11. Pickle and Unpickle'')
pickled = pickle.dumps(p)
unpickled = pickle.loads(pickled)
assert p == unpickled
print(''Pickled successfully/n'')
print(''12. Fields/n'')
print(''p: {}/n''.format(p._fields))
print(''13. Slots'')
print(''p: {}/n''.format(p.__slots__))
Salida en Python 2.7
1. Mutation of field values p: 10, 12 2. String p: Point(x=10, y=12) 3. Representation Point(x=10, y=12) 4. Sizeof size of p: 64 5. Access by name of field p: 10, 12 6. Access by index p: 10, 12 7. Iterative unpacking p: 10, 12 8. Iteration p: [10, 12] 9. Ordered Dict p: OrderedDict([(''x'', 10), (''y'', 12)]) 10. Inplace replacement (update?) p: Point(x=100, y=200) 11. Pickle and Unpickle Pickled successfully 12. Fields p: (''x'', ''y'') 13. Slots p: (''x'', ''y'')
La única diferencia con Python 3.5 es que la lista
namedlist
ha vuelto más pequeña, el tamaño es 56 (Python 2.7 informa 64).
Tenga en cuenta que he cambiado su prueba 10 para el reemplazo en el lugar.
El
namedlist
tiene un método
_replace()
que hace una copia superficial, y eso tiene mucho sentido para mí porque el
namedtuple
en la biblioteca estándar se comporta de la misma manera.
Cambiar la semántica del método
_replace()
sería confuso.
En mi opinión, el método
_update()
debería usarse para las actualizaciones
_update()
.
¿O tal vez no entendí la intención de tu prueba 10?