python - ¿Por qué el módulo namedtuple no utiliza una metaclase para crear objetos de clase nt?
metaclass python-internals (3)
Aquí hay otro enfoque.
""" Subclass of tuple with named fields """
from operator import itemgetter
from inspect import signature
class MetaTuple(type):
""" metaclass for NamedTuple """
def __new__(mcs, name, bases, namespace):
cls = type.__new__(mcs, name, bases, namespace)
names = signature(cls._signature).parameters.keys()
for i, key in enumerate(names):
setattr(cls, key, property(itemgetter(i)))
return cls
class NamedTuple(tuple, metaclass=MetaTuple):
""" Subclass of tuple with named fields """
@staticmethod
def _signature():
" Override in subclass "
def __new__(cls, *args):
new = super().__new__(cls, *args)
if len(new) == len(signature(cls._signature).parameters):
return new
return new._signature(*new)
if __name__ == ''__main__'':
class Point(NamedTuple):
" Simple test "
@staticmethod
def _signature(x, y, z): # pylint: disable=arguments-differ
" Three coordinates "
print(Point((1, 2, 4)))
Si este enfoque tiene alguna virtud, es la simplicidad. Sería más simple aún sin NamedTuple.__new__
, que sirve solo para el propósito de hacer cumplir el conteo de elementos. Sin eso, felizmente permite elementos anónimos adicionales más allá de los nombrados, y el efecto principal de omitir elementos es el IndexError
en elementos omitidos al acceder a ellos por nombre (con un poco de trabajo que podría traducirse a AttributeError
). El mensaje de error para un recuento de elementos incorrectos es un poco extraño, pero se identifica. No esperaría que esto funcionara con Python 2.
Hay espacio para complicaciones adicionales, como el método __repr__
. No tengo ni idea de cómo se compara el rendimiento con otras implementaciones (el almacenamiento en caché de la longitud de la firma podría ayudar), pero prefiero la convención de llamada en comparación con la implementación de namedtuple
nativo.
Pasé un tiempo investigando las collections.namedtuple
llamó módulo de doble hace unas semanas. El módulo utiliza una función de fábrica que llena los datos dinámicos (el nombre de la nueva clase de grupo con nombre y los nombres de atributo de clase) en una cadena muy grande. Luego, exec
se ejecuta con la cadena (que representa el código) como argumento, y se devuelve la nueva clase.
¿Alguien sabe por qué se hizo de esta manera, cuando hay una herramienta específica para este tipo de cosas fácilmente disponibles, es decir, la metaclase? No he intentado hacerlo yo mismo, pero parece que todo lo que está sucediendo en el módulo namedtuple
se podría haber logrado fácilmente usando una metaclase namedtuple
, así:
class namedtuple(type):
etcétera etcétera.
Como una nota al margen: la otra objeción que veo con más frecuencia contra el uso de exec
es que algunas ubicaciones (compañías de lectura) lo deshabilitan por razones de seguridad.
Además de un Enum
avanzado y NamedConstant
, la biblioteca aenum * también tiene NamedTuple
que se NamedTuple
metaclass
.
* aenum
está escrito por el autor de enum
y el enum34
backport.
Hay algunos consejos en el tema 3974 . El autor propuso una nueva forma de crear tuplas con nombre, que fue rechazada con los siguientes comentarios:
Parece que el beneficio de la versión original es que es más rápido, gracias a los métodos críticos de codificación. - Antoine Pitrou
No hay nada desagradable en el uso de exec. Las versiones anteriores utilizaron otros enfoques, resultaron ser innecesariamente complejos y tuvieron problemas inesperados. Es una característica clave para las tuplas con nombre que son exactamente equivalentes a una clase escrita a mano. - Raymond Hettinger
Además, aquí está la parte de la descripción de la receta original con nombre namedtuple
:
... la receta ha evolucionado a su estilo exec actual, donde obtenemos todos los argumentos integrados de alta velocidad de Python de forma gratuita. El nuevo estilo de construcción y ejecución de una plantilla hizo que las funciones __new__ y __repr__ sean más rápidas y limpias que en las versiones anteriores de esta receta.
Si estás buscando algunas implementaciones alternativas:
Resumen de clase base + mix-in para las tuplas con nombre receta por Jan Kaliszewski
Implementación basada en metaclase por Aaron Iles (ver su publicación en el blog )