python metaclass python-internals namedtuple

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: