ul-fmf / projekt-tomo

Spletna storitev za poučevanje programiranja
https://www.projekt-tomo.si
GNU Affero General Public License v3.0
14 stars 23 forks source link

Python settings stack #175

Closed jaanos closed 6 years ago

jaanos commented 6 years ago

Kot sva z @matijapretnar govorila v #172, je tukaj še implementacija sklada nastavitev za Python. Za uporabnika so relevantni sledeči context managerji:

Za nastavitev svežega okolja se lahko uporabi Check.set(env=okolje), kjer je okolje podano s slovarjem? Bo tako v redu, ali naj naredim novo metodo za ta namen?

Odstranil sem funkcijo visible_input znotraj output in namesto tega definiral razred VisibleStringIO, ki deduje od StringIO in izpiše to, kar se prebere. Tako se lahko s parametrom visible pri Check.input kontrolira, ali se bo podani vhod izpisoval (oziroma ali ga bo videl Check.output).

Odstranil sem tudi uporabo locals() - znotraj funkcije to namreč vrne slovar lokalnih spremenljivk, česar pa nočemo dajat testnim primerom.

matijapretnar commented 6 years ago
jaanos commented 6 years ago
jaanos commented 6 years ago

Še to: za končnega uporabnika je morda relevanten tudi Check.set_stringio, ki nastavi razred, s katerim se vstavlja input (sem dodal v zgornjem opisu) - potem ni potrebno pri vsakem Check.input nastavljat parametra visible. Ampak mogoče to ni najbolj posrečeno ime - bi bilo bolje Check.set_visible, pa se morda kar ohrani trenutna funkcionalnost (tako ali tako bo parameter večinoma True ali False)?

matijapretnar commented 6 years ago

Hmm. Kaj pa če bi tudi set_stringio, set_visible, set_clean in podobno dali v Check.set. Argumente za clean bi lahko tako kot v Djangu podali z Check.set(clean__digits=6), ali kaj takega.

Drugače se bojim, da postaja vse skupaj malo preveč zapleteno. A bi vprašali še koga drugega za mnenje?

jaanos commented 6 years ago

set_stringio po mojem lahko pridružimo, za set_clean pa nisem tako prepričan - se mi namreč zdi bolj čisto, če imamo metodo, ki nastavi ustrezen klic funkcije clean, kot pa da ena metoda to naredi, če so prisotni določeni parametri. Je pa tudi res, da bo kdo lahko naredil Check.set(digits = 2) in se čudil, zakaj ne deluje...

Še kakšno mnenje bi pa sigurno bilo dobrodošlo.

lokarM commented 6 years ago

Spremljam sicer ta razgovor, ampak sem se zgubil in pravzaprav ne vem točno, zakaj gre :-((. vsaj meni osebno bi zelo pomagalo, če bi videl kak konkreten primer uporabe - torej nalogo, rešitev in s temi novostmi pripravljene teste.

jaanos commented 6 years ago

Ideja je, da bi lahko z uporabo with nastavljali okolje, funkcijo clean in druge nastavitve, ki jih potem ne bi rabili ponavljati ob vsakem klicu metod iz razreda Check, npr.

with Check.set_environment(x=42, y=23): # nastavimo okolje pri izvajanju izrazov
    Check.equal('sestej(x, y)', 65)
    Check.equal('odstej(x, y)', 19)

with Check.set_clean(digits=3): # nastavimo število decimalk za natančnost primerjavi
    Check.equal('izracunaj_pi()', math.pi)
    Check.equal('izracunaj_e()', math.e)

with Check.set(should_stop=True): # generatorji se morajo ustaviti po želenem številu korakov
    Check.generator('naravna_stevila(3)', [1, 2, 3])
    Check.generator('naravna_stevila(5)', [1, 2, 3, 4, 5])

with Check.input(["vhod", "pericarežeracirep"], visible=True): # vhod naj se izpiše oz. ga vidi Check.output
    Check.output('zrcali_vhod()', ['> vhod', 'dohv'])
    Check.output('zrcali_vhod()', ['> pericarežeracirep', 'pericarežeracirep'])
lokarM commented 6 years ago

Predvsem 1, 2 in 4 se mi zdita zelo uporabna. Sicer za 4 si zadevo razlagam takole:

Check.output('zrcali_vhod()', ['> vhod', 'dohv'])

Verjetno je mišljeno, da je znotraj zrcali_vodi ukaz

input("> ")

In da je v funkciji le en input (drugega "pobere" drug Check.output)

=====================

Kaj se zgodi pri gnezdenju, npr.:

with Check.input(["vhod", "pericarežeracirep"], visible=True):
        with Check.input(["vhod", "pericarežeracirep"], visible=True): 

ali pa

with Check.input(["vhod", "pericarežeracirep"], visible=True):
    with Check.set_environment(x=42, y=23): 
             with Check.set_clean(digits=3):

Predvidevam, da ni težav?

jaanos commented 6 years ago

Tako, ja. Implementacija je pa taka, da imamo sklad nastavitev: pri vsakem with se na vrhu ustvari kopija trenutnih nastavitev z želenimi spremembami (set_environment poskrbi, da se nove spremenljivke dodajo k trenutnim); pri izstopu iz with se nastavitve z vrha sklada odstranijo in tako se vrne isto stanje kot pred vstopom.

jaanos commented 6 years ago

Glede Check.input pa je že prej bilo tako, da StringIO nadomesti karkoli je bilo prej nastavljeno za stdin, na koncu se pa potem vrne prejšnji objekt. V gnezdenem with bo torej viden samo trenutni vhod; po izstopu se potem nadaljuje vhod iz zunanjega with.

jaanos commented 6 years ago

Opazil sem, da se to zgodi v Python3:

>>> eval('[slovar[x] for x in [1, 2]]', globals(), {'slovar': {1: "a", 2: "b"}})
...
NameError: name 'slovar' is not defined

Za nas to pomeni, da s set_environment (ali s parametrom env) ne moremo nastaviti spremenljivk, ki se pojavijo v izpeljanih izrazih (ali pa funkcijah, lambdah in razredih). Še huje, tudi če se v izrazu (za exec) neki spremenljivki zamenja vrednost, se to opravi v lokalnem okolju in izpeljani izrazi tega ne vidijo (pri funkcijah se da vsaj priti okoli z global).

~Vem, da bi taka sprememba v tej točki prinesla same probleme, ampak a ne bi bilo bolj smiselno, da npr. Check.equal dobi kot argument kar rezultat klica izraza (torej ne izraza kot niz) in potem s tem dela naprej? V veliki večini primerov namreč to zadostuje - ko pa ne, pa bi lahko imeli eno funkcijo za izvajanje kode v drugačnih okoljih. Glede na to, da se preverjanja delajo na globalni ravni pri urejanju nalog, a na ravni funkcije pri reševanju, se pri trenutnem načinu dejansko pojavijo razlike: spremenljivke, nastavljene v testih, so vidne za eval/exec pri urejanju nalog (kjer jih dobi iz globals()), ne pa tudi pri reševanju (razen, če se jih eksplicitno navede pod env - kot rečeno zgoraj, pa potem še vedno ne delujejo v izpeljanjih izrazih itd.).~

matijapretnar commented 6 years ago

Ne razumem, kaj pomeni, da v veliki večini primerov zadostuje že rezultat izraza. V veliki večini primerov namreč želiš izpisati, pri katerem izrazu pride do napake, kajne?

jaanos commented 6 years ago

Ah, ne vem, kaj sem razmišljal, ko sem to pisal - očitno sem bil že precej zmeden:) Tako da sem zadnji odstavek zgoraj kar prečrtal. Še vedno pa ostaja problem iz prvega odstavka.

matijapretnar commented 6 years ago

Kaj pa, če bi uporabljali samo en slovar, torej:

>>> env = globals()
>>> env.update({'slovar': {1: "a", 2: "b"}})
>>> eval('[slovar[x] for x in [1, 2]]', env)
['a', 'b']

A kje v testih potrebujemo razliko med globals in locals?

jaanos commented 6 years ago

Verjetno bi šlo tako, ja. locals se v bistvu nanaša na kontekst znotraj funkcije (ali drugega izraza) tako da ga po mojem za potrebe klicanja testov niti ne bi rabili.

jaanos commented 6 years ago

Sem zdaj odstranil uporabo lokalnih okolij. Posledično sem odstranil use_globals, update_env pa zdaj deluje malo drugače. Če je True, se spremenljivke iz env dodajo v globals() (pred vsakim eval ali exec), in se to uporabi za poganjanje (in se torej lahko tudi spremeni). V nasprotnem primeru se naredi kopija globalnega okolja in posodobi z vrednostmi v env (kar je tudi privzeto delovanje).

Mimogrede, še ena zanimivost: če funkcija specificira spremenljivko z global, se uporabi spremenljivka iz Pythonovega globalnega okolja - ne glede na to, kaj je podano kot globalno okolje v eval/exec.

matijapretnar commented 6 years ago

Meni se zdi načeloma v redu. Je kar velika zadeva, tako da nisem čisto prepričan, da se ne bo nič polomilo, ampak po mojem lahko probamo in potem popravljamo.

jaanos commented 6 years ago

V redu - dobro bi bilo preveriti, če so kje kakšni testi, ki uporabljajo use_globals ali update_env, ker morda ne bodo več delovali pravilno.