Groovy on Grails

Questo framework si ispira anche onomatopeicamente a Ruby on Rails ed è la composizione di un linguaggio di programmazione per JVM (Groovy) su un framework MVC opzionalmente corredato da Spring (per il controllo della sicurezza) e Hibernate (lo strato ORM verso il database) che è Grails.

Il linguaggio di programmazione è Groovy che però è molto simile a Java, avendo poi rispetto questo una serie di “ammorbidimenti” (vedi l’articolo Programmare in Groovy).

Grails è un framework MVC che può essere associato a Spring per il controllo degli accessi e Hibernate come ORM. È un framework Open Source full-stack per applicazioni web basate su JVM. Adotta Apache Groovy come linguaggio di programmazione e il paradigma “meglio convenzione che configurazione” per consentire un’esperienza di programmazione estremamente produttiva e “aerodinamica”.

Ho scritto alcune guide come Creare un progetto Grails da linea di comando oppure con l’utilizzo della IDE Intellj IDEA di JetBrains. Uso la versione Ultimate che, tra le altre belle cose, ha che è possibile avere contemporaneamente anche un ambiente di sviluppo PHP attraverso l’attivazione di un plugin. In effetti JetBrains produce anche PHPStorm e quindi PHPStorm= IDEA + PHP Plugin.

Oltre a questo, anche una introduzione a Groovy come linguaggio di programmazione derivato da Java e una sezione di Q&A.

Programmare in Groovy

Groovy è un linguaggio fortemente tipizzato derivato da Java con in più le performance e la flessibilità di un linguaggio di scripting come Python ad esempio. È un derivato del Java con una nuova sintassi tale da renderlo molto familiare ai programmatori Java.

Le caratteristiche di Groovy risolvono una serie di problematiche di produttività ed eleganza con un buon compromesso sulla tipizzazione.

Essenzialmente è un linguaggio “Java light” che rispetto a Java ha alcuni rilassamenti:

  • Si possono definire i tipi delle variabili “in corsa” senza specificarne il tipo che viene dedotto al momento della compilazione (come avviene anche per PHP 7)
    def pi = 3.14
    e pi sarà un Float
  • La tipizzazione non è rigida come con Java, praticamente scompare la differenza tra classe e tipo primitivo:
    int a=1
    equivale a
    Integer a = 1
  • non sono necessari i ; alla fine dell’istruzione
  • overload degli operatori per gli oggetti. Ad esempio si potrà scrivere indifferentemente l’operatore == tra due int o due Integer, o addirittura tra un int e un Integer:
int a=2            // in Java sarebbe un tipo primitivo
print(a)

Integer b=2        // in Java sarebbe una classe
print(a.equals(b)) // stampa true
print(a==b)        // stampa true

Per testare costrutti Groovy si può utilizzare la consolle che si può far partire da IDE selezionando Tools > Groovy console:

e cliccando sulla freccetta bianca in alto a sinistra si compila ed esegue il programma:

Tips on Grails programming

Ci sono alcune pratiche utili che consentono di sviluppare in modo coerente.

Io sono partito da questa esigenza: voglio che ogni modello (o dominio come viene chiamato in Grails) debba possedere uno standard, ad esempio voglio che tutti i modelli siano corredati dai campi ch riportano lo username del creatore dell’oggetto, e i timestamp di creazione e modifica.d

Grails ha il notevole supporto ai template per ogni oggetto del paradigma MVC: dai model, ai controller alle alle view.

Per installare nella propria applicazione i template si ricorre al comando

$ grails install-templates
| Templates installed successfully
$

Questo comando semplicemente copia i template dell’installazione di Grails dentro alla cartella src/templates e si suddivide in quattro cartelle:

  • artifacts che contiene i file Controller.groovy, DomainClass.groovy, Filters.groovy, …
  • scaffolding che contiene le pagine gsp di layout delle views standard

Pillole Linux: il comando wget

wget è un programma Linux che effettua il download di una risorsa web agendo allo stesso modo dei crawlers dei motori di ricerca e la deposita in un file in locale nel computer in cui si è lanciato il comando.

Digitando

$ wget https://www.mysite.org

scarico in locale la pagina indice:

marcob@jsbach:mysite$ ll
 totale 20
 drwxr-xr-x 2 marcob marcob  4096 ago  7 15:01 ./
 drwxr-xr-x 4 marcob marcob  4096 ago  7 15:00 ../
 -rw-r--r-- 1 marcob marcob 11395 ago  7 15:01 index.html

Se c’è una gerarchia di directory posso scaricare anche quella, cioè scaricare tutti i file le directory e le sottodirectory, con l’opzione -r (recursive) e specificando il numero di livelli di gerarchia a cui voglio scendere al massimo; per esempio se voglio fermarmi al terzo:

$ wget -r -l3 https://www.mysite.org

Spesso però i siti vengono impostati in modo da bloccare questo tipo di download massivo, ma wget è molto potente: possiamo inviare nella richiesta anche degli header HTTP personalizzati con l’opzione ‐‐header, ad esempio fingendoci un browser vero:

$ wget -r -l3 --no-parent --header="Accept: text/html" --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0" https://www.mysite.org

Tuttavia il web server può sempre bloccare lo user agent quando il numero di pagine richieste al secondo è troppo elevata. Con queste opzioni ci presentiamo al server come se fossimo un browser Firefox per sistema operativo Mac OS X. Inoltre specificando ‐‐no-parent evitiamo di fare il download anche delle eventuali cartelle soprastanti quella di cui vogliamo fare il download.

Come ultimo esempio, potendo inviare header HTTP a piacere, possiamo inviare l’header di autenticazione per siti protetti con autenticazione. Ovviamente dobbiamo avere un account in quel sito e possiamo evitare di mandare in chiaro la password in http utilizzando l’opzione ‐‐ask-password:

$ wget -r -l10 --no-parent --user=myuser --ask-password --header="Accept: text/html" --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0" http://www.mysite.org

Pillole Oracle: altro esempio con WITH

Un utilizzo un po’ meno banale di quello illustrato nel primo post sulla clausola WITH è quello che alla fine ho utilizzato per riuscire in un task del genere:

Come faccio a produrre un recordset contente le date di una settimana da domenica a sabato dato che sia un giorno qualsiasi della settimana che si vuole estrarre?

Utilizzando la clausola WITH possiamo costruirci una tabella temporanea arr_dates con un’unica colonna arr_date:

Ora questo costrutto ha una sua complessità. Partiamo individuando i blocchi:

  • la clausola WITH definisce un recordset temporaneo arr_dates
  • contente il campo arr_date
  • il recordset è definito come segue: seleziona (riga 2) il sabato successivo (+5) al parametro data (:1)
  • union all
  • seleziono (riga 4) il giorno precedente (-1) dal recordset definito sopra con la condizione che la data deve essere maggiore della data di inizio della settimana della data parametro (:1); questa condizione è soddisfatta da tutti i giorni dall’inizio della settimana di partenza alla domenica successiva.
  • alla fine (riga 9) c’è una semplice selezione sull’oggetto appena costruito

Quindi l’effetto è quello di selezionare 7 “record” centrati attorno al giorno :1. Ad esempio se prendiamo come parametro il 2 luglio 2019 ho

Stesso recordset se come parametro scegliamo il 5 luglio 2019. Se invece scegliamo come “centro” il 10 luglio 2019:

Alla fine possiamo calcolare questi recordset inglobando il tutto in una function ma lo vediamo nel prossimo post.

Pillole Oracle: la clausola WITH

Questa clausola, introdotta a partire dalla versione Oracle 9.2, consente di eseguire una query in una tabella utilizzando come parametro il risultato di un’altra query.

Come esempio immaginiamo di avere una tabella di rilievi di temperature e voler selezionare i record in cui i valori sono superiori alla media.

create table temperature(
  id number(10),
  valore number(10,2),
  data_rilievo timestamp
);

Aggiungiamo un po’ di dati:

IDVALORETIMESTAMP
124,301-LUG-19 10:26:15,820000000
228,202-LUG-19 10:26:43,310000000
334,803-LUG-19 10:26:57,810000000
428,704-LUG-19 10:27:15,819000000
532,505-LUG-19 10:27:30,714000000

Una possibile soluzione per questo problema è la query seguente:

select valore from temperature
where valore > (
  select avg(valore) from temperature
)

che da’ il seguente recordset

VALORE
34,8
32,5

Con la clausola WITH costruiamo una tabella temporanea con una sola colonna e una sola riga che contiene il valore medio. Potremo successivamente riferirci al valore contenuto in questo recordset per ritornare il recordset ottenuto con la query standard di prima:

WITH tmpTemperature (tmpMedia) as
    (SELECT avg(valore) FROM Temperature)

SELECT valore
FROM Temperature, tmpTemperature
WHERE Temperature.valore > tmpTemperature.tmpMedia;

In sostanza si tratta di definire l’oggetto temporaneo che è un recordset con una riga e una colonna; poi si esegue la query sulla tabella delle temperature utilizzando la tabella temporanea definita in virtù della clausola WITH.

Il recordset ritornato è identico a quello di prima:

VALORE
34,8
32,5

Quindi fondamentalmente la sintassi della clausola WITH è

WITH tabellaTemporanea (parametro) AS
    (select di calcolo del parametro)

SELECT * 
FROM tabella1, tabella2, ..., tabellaTemporanea 
WHERE 
    ... condizione su tabellaTemporanea.parametro...
;

Pillole di Internet: come viaggiano in rete i file binari trasmessi con HTTP?

Ho scritto una semplice pagina web che invia un file (ho scelto una immagine PNG piccolina) al mio localhost. L’HTML di questa pagina è il seguente

 <form action="upload.php" 
      enctype="multipart/form-data" 
       method="post">
      <input type="file" name="file">
      <input type="submit">
 </form>

Catturando con Wireshark il traffico HTTP rilevo:

Questo è quello che si osserva una volta che si preme il pulsante Invia del modulino, dopo aver selezionato l’immagine. In particolare, di tutto il messaggio HTTP che viene compilato dal browser ho qui riportato l’inizio della parte contenente l’immagine, che è l’informazione binaria che vogliamo capire come viaggia in rete.

In particolare possiamo notare, leggendo la colonna centrale del dump riportato da Wireshark, che in HTTP non passa solo testo ma anche informazioni puramente binarie. I singoli bytes sono a volte superiori a 7F (=011111112) – e i caratteri codificati in ASCII standard sono a 7 bit – per cui si tratta di bytes a 8 bit (con il bit più significativo a 1) e non 7 (cioè con il bit più significativo a 0). Non c’è inoltre alcuna applicazione di una codifica base64 ai bytes che compongono il messaggio.

Da Internet la RFC-2388 recita:

 4.3 Encoding

  While the HTTP protocol can transport arbitrary binary data, the
  default for mail transport is the 7BIT encoding.  The value supplied
  for a part may need to be encoded and the "content-transfer-encoding"
  header supplied if the value does not conform to the default
  encoding.      

Quindi, a imitazione del protocollo SMTP, si potrebbe codificare il file (che nella mail è l’allegato) utilizzando la codifica base64, ma in questo caso la codifica adottata è 8 bit. La scelta della codifica è ampia, come si desume dalla grammatica di encoding riportata nella rfc-1521:

        encoding := "Content-Transfer-Encoding" ":" mechanism

           mechanism :=     "7bit"  ;  case-insensitive
                          / "quoted-printable"
                          / "base64"
                          / "8bit"
                          / "binary"
                          / x-token 

In sintesi: il browser costruisce un file che contiene tutte le parti (multipart) contenute nei campi della form (multipart/form-data in questo caso solo due, l’immagine ed il pulsante di invio); in questo file tutto ciò che è testo rimane testo e ciò che è binario rimane binario; ogni elemento del form è separato dagli altri da un delimitatore (lo potete notare all’inizio della colonna di destra nell’immagine della cattura Wireshark):

——WebKitFormBoundaryXxWGBqDWd6OsDPE2

(per questo il tipo di encoding, o enctype, utilizzato dal browser è il multipart/form-data)

Le informazioni di intestazione che precedono il messaggio HTTP sono

POST /test/upload.php HTTP/1.1
 Host: js
 Connection: keep-alive
 Content-Length: 3840
 Cache-Control: max-age=0
 Origin: http://js
 Upgrade-Insecure-Requests: 1
 DNT: 1
 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXxWGBqDWd6OsDPE2
 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.28 Safari/537.36
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
 Referer: http://js/test/post.php
 Accept-Encoding: gzip, deflate
 Accept-Language: it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7,fr;q=0.6,la;q=0.5

Sul significato di tutti questi parametri a breve faccio un altro post.

Nota bene: per poter fare un’analisi simile dobbiamo mantenere in chiaro la trasmissione (non usare HTTPS)

Problema failed (13: Permission denied) [CentOS + nginx]

Un fastidioso errore 404 mi risultava dalla visita di uno dei miei siti, e mi sembrava che l’attribuzione dei permessi fosse corretta.

L’errore HTTP 404 viene emesso dal server quando non trova nel file system la risorsa (file o cartella) corrispondente all’URI che gli viene passato dal client (ad esempio Google Chrome o Firefox): il famoso errore di Pagina non trovata.

Questo errore si verifica anche se il file c’è ma si verifica una di queste condizioni:

  • l’utente sotto il quale sta girando il server non ha permessi di lettura sul file desiderato né come utente né come gruppo
  • non ha la possibilità di raggiungere quella risorsa percorrendo il grafo della directory da / al file desiderato

In realtà basta infatti che una directory della catena di “cd command” (change directory) che il servizio nginx deve fare e si verifica l’errore. Il modo più efficiente di vedere lo stato di tutta la gerarchia di directory è utilizzare il comando namei:

namei - follow a pathname until a terminal point is found
[root@websrv01 site]# namei -om /usr/share/nginx/html/
f: /usr/share/nginx/html/
 dr-xr-xr-x root root       /
 drwxr-xr-x root root       usr
 drwxr-xr-x root root       share
 drwxr-xr-x root root       nginx
 drw-rwSr-- root developers html

Qui è evidente il problem: tutta la catena di directory ha l’opzione x (eXecute) tranne la directory contenente la DocRoot di nginx; ragion per cui è sufficiente aggiungere questo permesso:

[root@websrv01 site]# chmod a+x /usr/share/nginx/html/
[root@websrv01 site]# namei -om /usr/share/nginx/html/
f: /usr/share/nginx/html/
 dr-xr-xr-x root root       /
 drwxr-xr-x root root       usr
 drwxr-xr-x root root       share
 drwxr-xr-x root root       nginx
 drwxrwsr-x root developers html
[root@websrv01 site]# 

Il problema è risolto.

Pillole Laravel: esecuzione dei test con PHPUnit

Devo testare delle API con Unit Test di una classe Category. Come primo passaggio genero la classe di Test:

php artisan make:test CategoryTest

Questo comando mi genera un file sotto tests/Feature/CategoryTest.php che tosto personalizzo come segue, un metodo per ogni operazione del CRUD:

<?php

namespace Tests\Feature;

use App\Category;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class CategoryTest extends TestCase
{

    public function testsCategoriesAreCreatedCorrectly()
    {
        $headers = ['Authorization' => "ajs8ebddau"];

        $payload = [
            'name' => 'Libri e Riviste INS',
            'short' => 'libri INS',
        ];

        $this->json('POST', '/api/categories', $payload, $headers)
            ->assertStatus(302)
            ->assertJson([
                'name' => 'Libri e Riviste INS',
                'short' => 'libri INS'
            ]);
    }

    public function testsCategoriesAreUpdatedCorrectly()
    {
        $headers = ['Authorization' => "ajs8ebddau"];

        $category = new Category;
        $category->name = 'Libri e Riviste TEST';
        $category->short = 'libri TEST';
        $category->save();

        $id = $category->id;

        $payload = [
            'name' => 'Libri e Riviste UPDATE',
            'short' => 'libri UPDATE',
        ];

        $response = $this->json('PUT', '/api/categories/' . $id, $payload, $headers)
            ->assertStatus(200)
            ->assertJson([
                'id' => $id,
                'name' => 'Libri e Riviste UPDATE',
                'short' => 'libri UPDATE'
            ]);
    }

    public function testsCategoriesAreDeletedCorrectly()
    {

        $headers = ['Authorization' => "ajs8ebddau"];

        $id = 30;

        $payload = [
            'name' => 'Lorem',
            'short' => 'Ipsum',
        ];

        $category = new Category;
        $category->name = 'Libri e Riviste DEL';
        $category->short = 'libri DEL';
        $category->save();
        $id = $category->id;

        $response = $this->json('DELETE', '/api/categories/' . $id, $payload, $headers)
            ->assertStatus(200);
    }

    public function testCategoriesAreListedCorrectly()
    {
        $category = new Category;
        $category->name = 'Libri e Riviste first';
        $category->short = 'libri first';
        $category->save();

        $category = new Category;
        $category->name = 'Libri e Riviste second';
        $category->short = 'libri second';
        $category->save();

        $headers = ['Authorization' => "ajs8ebddau"];

        $response = $this->json('GET', '/api/categories', [], $headers)
            ->assertStatus(200)
            ->assertJson([
                [ 'name' => 'Libri e Riviste first', 'short' => 'libri first' ],
                [ 'name' => 'Libri e Riviste second', 'short' => 'libri second' ]
            ])
            ->assertJsonStructure([
                '*' => ['id', 'name', 'short', 'created_at', 'updated_at'],
            ]);
    }
}

In realtà la variabile $headers qui è superflua, ma mi viene comoda dopo, quando aggiungerò lo strato di autenticazione.

Eseguo i test

marcob@jsbach:shopping3$ composer test
> vendor/bin/phpunit
PHPUnit 7.4.0 by Sebastian Bergmann and contributors.

.....                                                               5 / 5 (100%)

Time: 248 ms, Memory: 16.00MB

OK (5 tests, 7 assertions)
marcob@jsbach:shopping3$ 

Il comando di test qui è dato utilizzando composer, in quanto ho definito uno script “test” all’interno del file di configurazione composer.json: alla sezione script ho aggiunto:

    "scripts": {
        ...
        "test" : [
            "vendor/bin/phpunit"
        ]
    },

L’alternativa è scrivere semplicemente vendor/bin/phpunit

Interferometria e codice Python

Ecco un’istantanea dell’articolo che descrive l’algoritmo sviluppato da Katie Bouman e altri (due del MIT, due di Harvard e uno di Google) che è stato utilizzato per ricomporre l’immagine del buco nero.

L’articolo risale al 2016, era stato presentato alla IEEE Conference on Computer Vision and Pattern Recognition ed è stato reso di dominio pubblico grazie alla Computer Vision Foundation. In calce il link al pdf dell’articolo e anche il link a Github con i sorgenti del software di analisi utilizzato per produrre l’immagine del buco nero. Tra i contributi della Bouman ad esempio il commit #73. Ma come tutti i progetti importanti, ci hanno lavorato in tanti 😊

https://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/Bouman_Computational_Imaging_for_CVPR_2016_paper.pdf

https://github.com/achael/eht-imaging

Pillole di Python: la direttiva @property

Con la direttiva @property in Python è possibile pubblicare i getter e i setter per le proprietà della classe senza doverli invocare esplicitamente, bensì utilizzando la variabile stessa. Nell’ambito Python il nome di queste direttive è decoratori. In realtà, come vedremo, si tratta solo di una pseudo variabile.

Esempio: una classe che prende un valore di temperatura intesa in gradi Celsius e lo trasforma in Fahrenheit:

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

Se utilizziamo questa classe otteniamo:

>>> from celsius0 import Celsius
>>> c=Celsius(10)
>>> c.temperature
10
>>>

Ora rilasciamo la versione nel nostro software. Subito dopo ci vengono a dire che il modulo accetta temperature sotto lo 0 assoluto (e questo è francamente imbarazzante), per cui rilasciamo subito questa patch che utilizza una proprietà privata (quella contrassegnata col prefisso _) e i get/setters per consentire il controllo del valore di ingresso:

class Celsius:
    def __init__(self, temperature = 0):
        self.set_temperature(temperature)

    def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32

    # getter & setters
    def get_temperature(self):
        return self._temperature

    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature sotto -273 non sono ammesse")
        self._temperature = value

Ora però il nostro codice non è più retro compatibile e se qualcuno per caso ha usato la nostra classe, quando userà una istruzione del tipo

>>> from celsius import Celsius
>>> c = Celsius(100)
>>> c.temperature

Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Celsius' object has no attribute 'temperature'

Ohi ohi. In effetti adesso per permettere il controllo abbiamo trasferito il valore alla variabile privata _temperature, perdendo per strada la variabile temperature. Ma Python ci permette di risolvere il problema dichiarando una pseudo-proprietà temperature che sostituisce i getter e i setter e fa in modo che dall’esterno si osservi effettivamente una proprietà (nel senso della variabile della classe) che si chiama nel vecchio modo, ossia della prima versione dl software (temperature); questo senza rinunciare alla proprietà privata che ci consente il controllo che il valore non sia sotto lo zero assoluto. Per questa ultima versione utilizziamo anche un file di constanti fisiche per portare il software al miglior grado di modularità:

# celsius.py
# A silly class to show the @property decorator
# allowing to implicitly define get/setters
#
############################################################
# Celsius object
############################################################

import constants
K0 = constants.KELVIN0

class Celsius:
    def __init__(self, temperature = 0):
        try:
             if temperature < K0:
                 raise ValueError
             self._temperature = temperature
        except ValueError:
           print("Temperature sotto " + str(K0) + " non permesse")

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Acquisizione valore")
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        try:
             if value < K0:
                 raise ValueError
             self._temperature = value
        except ValueError:
           print("Temperature sotto " + str(K0) + " non permesse")

Qualche commento:

  • Per evitare fastidiosi messaggi di traceback del runtime environment di Python utilizziamo il costrutto try/except che ci consente una più robusta gestione delle eccezioni
  • utilizziamo le costanti importate dal file constants.py in cui ho definito una serie di costanti fisiche
  • definiamo la sezione @property che ci consente di invocare il getter semplicemente utilizzando la pseudo variabile
  • definiamo la sezione @temperature.setter che ci consente di sostituire il setter

Proviamo la nuova versione del nostro software

marcob@jsbach:python$ python3
Python 3.5.2 (default, Nov 12 2018, 13:43:14)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from celsius import Celsius
>>> c=Celsius(-2000)
Temperature sotto -273.15 non permesse
>>> c=Celsius(-20)
>>> c.temperature <-- getter!
Acquisizione valore
-20
>>> c.temperature = 130 <-- setter!
>>> c.temperature
Acquisizione valore
130

Quindi @property è un modo di sostituire getter e setter in modo tale da utilizzare nominalmente la stessa variabile come se fosse una vera proprietà della classe (in effetti non esiste, esiste soltanto l’attributo privato _temperature).

Possiamo anche trovare l’utilizzo del decoratore @property sbirciando nel codice eht-imaging, il software Python scritto per ricostruire l’immagine del buco nero nell’ambito della collaborazione Event Horizon Telescope

Ad esempio nel file movie.py c’è la dichiarazione di questa proprietà frames:

    @property
    def frames(self):
        frames = self._movdict[self.pol_prim]
        return frames

    @frames.setter
    def frames(self, frames):
        if len(frames[0]) != self.xdim*self.ydim:
            raise Exception("imvec size is not consistent with xdim*ydim!")
        #TODO -- more checks on consistency with the existing pol data???

        self._movdict[self.pol_prim] =  frames

Il getter: quando viene menzionata la pseudo variabile frames, ritorna l’attributo privato dell’oggetto self._movdict[self.pol_prim].

Il setter: dopo un controllo di consistenza, valorizza l’attributo self._movdict[self.pol_prim] con il valore assegnato alla pseudo variabile frames.