[Python] typing.NamedTuple - valori di default con definizione funzionale

Marco Giusti marco.giusti a posteo.de
Mer 9 Mar 2022 10:00:34 CET


On 09.03.2022 00:45, Pietro Convalle wrote:
> Si, probabilmente la cosa migliore e' ridefinire __new__
> 
> ma invece di aggiungerla dopo la creazione prova ad aggiungerla durante
> la creazione concatenando i valori default prima o dopo il for
> 
> ho anche provato ad aggiungere questa modifica dopo la creazione di
>> Links, ma non ha effetto.
>> 
>> Links._field_defaults = {
>>     cardinal_point.value: None
>>     for cardinal_point in CARDINAL_POINTS_6_NS
>> }
>> 
>> suppongo che provare a ridefinire __new__ possa essere la
>> soluzione...

Incuriosito dalla tua domanda e curioso di scoprirne di più su Python3
ho investigato sull'implementazione di typing.NamedTyple.

Non sono sicuro questo sia una buona soluzione per la ragione che accedo
ad un attributo privato del modulo typing. In aggiunta, anche se non
conosco il contesto e non è questo quello che hai chiesto, mi domando se
avere una classe dinamica sia una buona soluzione. Come linea di
principio ritengo le meta-classi qualcosa da evitare e spesso si hanno
delle soluzioni più semplici.

Questo è un utilizzo tradizionale di typing.NamedTyple:

     $ python3.10
     Python 3.10.2 (main, Feb 15 2022, 12:33:54) [GCC 10.2.1 20210110] on 
linux
     Type "help", "copyright", "credits" or "license" for more 
information.
     >>> import typing
     >>> class Point(typing.NamedTuple):
     ...  x: int
     ...  y: int = 0
     ...
     >>> Point.__annotations__
     {'x': <class 'int'>, 'y': <class 'int'>}
     >>> Point.y
     _tuplegetter(1, 'Alias for field number 1')
     >>> Point.x
     _tuplegetter(0, 'Alias for field number 0')

Il primo tentativo è stato quello di utilizzare type per create
dinamicamente una classe. Questo non ha funzionato, ma conosco troppo
poco le annotations e tutto quello che ne ha derivato per dare un
qualche commento.

     >>> Point2 = type('Point2', (typing.NamedTuple, ), 
{'__annotations__': {'x': int, 'y': int}, 'y': 2})
     Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
     TypeError: type() doesn't support MRO entry resolution; use 
types.new_class()

Come consigliato dal messaggio di errore ho provato ad utilizzare
types.new_class, ma questa non permette di definire gli attributi della
nuova classe, necessari per definire i valori di default.


     >>> import types
     >>> Point2 = types.new_class('Point2', (typing.NamedTuple, ), 
{'__annotations__': {'x': int, 'y': int}, 'y': 2})
     Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
       File "/home/marco/.pyenv/versions/3.10.2/lib/python3.10/types.py", 
line 77, in new_class
         return meta(name, resolved_bases, ns, **kwds)
     TypeError: NamedTupleMeta.__new__() got an unexpected keyword 
argument '__annotations__'

Il terzo tentativo è stato quello di chiamare la meta-classe
direttamente. Ottengo il risultato voluto, ma come vedi devo accedere
ad un attributo privato. Non sono sicuro perché NamedTupleMeta verifica
le classi basi, ma non neanche compito mio domandarmi perché.

     >>> Point2 = typing.NamedTupleMeta('Point2', (typing._NamedTuple, ), 
{'__annotations__': {'x': int, 'y': int}, 'y': 2, '__module__': 
'__main__'})
     >>> Point2
     <class '__main__.Point2'>
     >>> Point2()
     Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
     TypeError: Point2.__new__() missing 1 required positional argument: 
'x'
     >>> Point2(1)
     Point2(x=1, y=2)
     >>>





Maggiori informazioni sulla lista Python