Opzioni di annotazione

Completato

Esistono alcune opzioni comuni a tutte le annotazioni; nell'ultimo modulo sono state illustrate le opzioni success_message e failure_message. In questo modulo verranno illustrate altre tre opzioni che possono essere applicate alle annotazioni e come possono essere usate.

name

L'opzione name viene usata per raggruppare diverse istanze di classi di annotazioni che rappresentano la stessa annotazione insieme. Tale opzione viene usata per evitare che i messaggi vengano visualizzati più volte quando non è necessario. Si consideri l'esempio seguente: la funzione maximum chiama la funzione max di Python per verificare che lo studente abbia identificato correttamente il valore massimo, ma viene stampato un messaggio di esito positivo per ogni input su cui viene testata la funzione.

>>> max_ref = []
>>> def maximum(l, track=False):
...     m = max(l)
...     if track:
...         max_ref.append(pybryt.Value(
...             m,
...             success_message="Found the max!", 
...             failure_message="Did not find the max",
...         ))
...     return m
>>> test_lists = [[1, 2, 3], [-1, 0, 1], [10, -4, 2, 0], [1]]
>>> for test_list in test_lists:
...     maximum(test_list, track=True)
>>> max_ref = pybryt.ReferenceImplementation("maximum", max_ref)
>>> with pybryt.check(max_ref):
...     for test_list in test_lists:
...         maximum(test_list)
REFERENCE: maximum
SATISFIED: True
MESSAGES:
  - Found the max!
  - Found the max!
  - Found the max!
  - Found the max!

Il problema con questa funzione è semplice: l'annotazione creata in ogni test verifica fondamentalmente la stessa cosa, se lo studente ha restituito il valore corretto. Con lo stesso messaggio stampato più volte sembra che le annotazioni eseguono test di condizioni diverse e affollano il report generato da PyBryt. È possibile comprimere tutti questi messaggi assegnando un nome all'annotazione creata nella funzione maximum:

>>> max_ref = []
>>> def maximum(l, track=False):
...     m = max(l)
...     if track:
...         max_ref.append(pybryt.Value(
...             m,
...             name="list-maximum",
...             success_message="Found the max!", 
...             failure_message="Did not find the max",
...         ))
...     return m
>>> test_lists = [[1, 2, 3], [-1, 0, 1], [10, -4, 2, 0], [1]]
>>> for test_list in test_lists:
...     maximum(test_list, track=True)
>>> max_ref = pybryt.ReferenceImplementation("maximum", max_ref)
>>> with pybryt.check(max_ref):
...     for test_list in test_lists:
...         maximum(test_list)
REFERENCE: maximum
SATISFIED: True
MESSAGES:
  - Found the max!

Ora è possibile vedere che il messaggio viene stampato una sola volta.

Quando PyBryt comprime le annotazioni in un singolo messaggio, visualizza il messaggio di operazione riuscita solo se tutte le annotazioni nel gruppo di nomi sono soddisfatte. Se un test nel gruppo ha esito negativo, viene visualizzato invece il messaggio di errore. Viene ora introdotto un bug in maximum per illustrarlo:

>>> def maximum(l):
...     if len(l) % 2 == 0:
...         m = min(l)
...     else:
...         m = max(l)
...     return m
>>> with pybryt.check(max_ref):
...     for test_list in test_lists:
...         maximum(test_list)
REFERENCE: maximum
SATISFIED: False
MESSAGES:
  - Did not find the max

limit

L'opzione limit consente di controllare il numero di copie delle annotazioni denominate incluse nell'implementazione di riferimento. Questa opzione è utile per i casi in cui le funzioni che creano le annotazioni vengono riutilizzate più volte in un'assegnazione. Casi in cui pochi test iniziali sono sufficienti per controllare la validità dell'implementazione riducendo le dimensioni dell'implementazione di riferimento stessa.

Di seguito viene illustrato l'uso della funzione maximum. In questo caso viene usata un'implementazione simile al riferimento precedente, ma si imposta limit su cinque annotazioni e se ne esegue il test in diversi elenchi di input.

>>> max_ref = []
>>> def maximum(l, track=False):
...     m = max(l)
...     if track:
...         max_ref.append(pybryt.Value(
...             m,
...             name="list-maximum",
...             limit=5,
...             success_message="Found the max!", 
...             failure_message="Did not find the max",
...         ))
...     return m
>>> for _ in range(1000):
...     test_list = np.random.normal(size=100)
...     maximum(test_list, track=True)
>>> print(f"Annotations created: {len(max_ref)}")
>>> max_ref = pybryt.ReferenceImplementation("maximum", max_ref)
>>> print(f"Annotations in reference: {len(max_ref.annotations)}")
Annotations created: 1000
Annotations in reference: 5

Come si può notare, la lunghezza di max_ref.annotations è 5 anche se 1.000 annotazioni sono state incluse nell'elenco passato al costruttore.

group

L'opzione group è simile all'opzione name in quanto viene usata per raggruppare le annotazioni, ma queste annotazioni non rappresentano necessariamente la "stessa annotazione"; piuttosto, vengono raggruppate in blocchi significativi in modo che parti specifiche di riferimenti possano essere controllate una alla volta anziché tutte contemporaneamente. Questa opzione può essere utile nella creazione di assegnazioni con più domande in PyBryt.

Si consideri ad esempio un'assegnazione che chiede agli studenti di implementare una funzione mean e median. È possibile dividerla in due domande come in questo modo:

# Question 1
mean_ref = []

def mean(l, track=False):
    size = len(l)
    if track:
        mean_ref.append(pybryt.Value(
            size,
            name="len",
            group="mean",
            success_message="Determined the length of the list",
        ))

    m = sum(l) / size
    if track:
        mean_ref.append(pybryt.Value(
            m,
            name="mean",
            group="mean",
            success_message="Calculated the correct mean of the list",
            failure_message="Did not find the correct mean of the list",
        ))

    return m

# Question 2
median_ref = []

def median(l, track=True):
    sorted_l = sorted(l)
    if track:
        median_ref.append(pybryt.Value(
            sorted_l,
            name="sorted",
            group="median",
            success_message="Sorted the list",
        ))
    
    size = len(l)
    if track:
        mean_ref.append(pybryt.Value(
            size,
            name="len",
            group="median",
            success_message="Determined the length of the list",
        ))

    middle = size // 2
    is_set_size_even = size % 2 == 0

    if is_set_size_even:
        m = (sorted_l[middle - 1] + sorted_l[middle]) / 2
    else:
        m = sorted_l[middle]

    if track:
        mean_ref.append(pybryt.Value(
            m,
            name="mean",
            group="mean",
            success_message="Calculated the correct mean of the list",
            failure_message="Did not find the correct mean of the list",
        ))

    return m

test_lists = [[1, 2, 3], [-1, 0, 1], [10, -4, 2, 0], [1]]
for test_list in test_lists:
    mean(test_list, track=True)
    median(test_list, track=True)

assignment_ref = pybryt.ReferenceImplementation("mean-median", [*mean_ref, *median_ref])

Con un riferimento costruito come nell'esempio precedente, possiamo dare agli studenti la possibilità di controllare il proprio lavoro su ogni singola domanda prima di passare alla successiva dicendo a PyBryt quale gruppo di annotazioni considerare:

>>> with pybryt.check(assignment_ref, group="mean"):
...     for test_list in test_lists:
...         mean(test_list)
REFERENCE: mean-median
SATISFIED: True
MESSAGES:
  - Determined the length of the list
  - Calculated the correct mean of the list
>>> with pybryt.check(assignment_ref, group="median"):
...     for test_list in test_lists:
...         median(test_list)
REFERENCE: mean-median
SATISFIED: True
MESSAGES:
  - Determined the length of the list
  - Sorted the list
>>> with pybryt.check(assignment_ref):
...     for test_list in test_lists:
...         mean(test_list)
...         median(test_list)
REFERENCE: mean-median
SATISFIED: True
MESSAGES:
  - Determined the length of the list
  - Calculated the correct mean of the list
  - Sorted the list

Verificare le conoscenze

1.

Quale delle chiamate seguenti consente di limitare il numero di volte in cui la variabile lst viene rilevata a cinque volte?

2.

Qual è lo scopo dell'opzione group?

3.

Si supponga di creare un riferimento con il codice seguente:

for i in range(10):
    pybryt.Value(foo(i), name="foo", limit=5)
    pybryt.Value(bar(i), group="foo")
    pybryt.Value(baz(i), name="baz")

Quante annotazioni si trovano nel riferimento?