Nie zrobię ci nic złego

Wczo­raj­szy prze­druk w Ars Tech­nice poka­zuje cie­kawe zja­wi­sko. Im bar­dziej użyt­kow­nicy są prze­ko­ny­wani o pouf­no­ści i bez­pie­czeń­stwie swo­ich danych, z tym więk­szym tru­dem przy­cho­dzi im owe dane podać.

Jeśli się nad tym jed­nak zasta­no­wić, oka­zuje się, że jest to zwią­zane z cał­kiem nor­mal­nym funk­cjo­no­wa­niem mózgu. Ktoś pamięta skecz o pilo­tach w wyko­na­niu Monty Pythona?

— Tu mówi wasz kapi­tan, nie ma abso­lut­nie żadnych powo­dów do paniki.

— …

— Skrzy­dła wcale się nie palą!

Naj­istot­niej­szą rolę odgrywa tutaj sam fakt wspo­mnie­nia o nie­wy­stą­pie­niu okre­ślo­nego czyn­nika. Czy kto­kol­wiek z was jest sobie w sta­nie wyobra­zić luna­park z kolejką gór­ską, przy któ­rej stoi poniż­sza tabliczka?

Inspek­cja tech­niczna zapew­nia, iż w wyniku korzy­sta­nia z kolejki nie zgi­niesz powolną śmier­cią, miaż­dżony przez tony gię­tych z potwor­nym zgrzy­tem rur i roz­cięty na pół bla­chą opa­trzoną logo naszego parku rozrywki.

„Dla­czego w ogóle miał­bym o tym pisać?” Brzmi idio­tycz­nie, prawda? „Z pew­no­ścią nikt roz­sądny nie zro­biłby nic podobnego!”

Tym­cza­sem każ­dego dnia wysta­wiamy swoje małe tabliczki:

Nasz ser­wis jest wyjąt­kowo bez­pieczny. Nikomu nie udo­stęp­nimy two­jego adresu e-mail. Ani żadnych innych two­ich danych. Posta­ramy się dzia­łać aku­rat wtedy, kiedy będziesz nas potrze­bo­wał. A w razie czego mamy kopie zapasowe.

A poten­cjalny klient czyta i… zamyka okno przeglądarki.

Button kontra przeglądarki

Ku pamięci:

  • Fire­fox¹ igno­ruje line-height.

  • Fire­fox¹ dodaje wła­sny ele­ment z dwu­pik­se­lo­wym pad­din­giem i pik­se­lo­wym borderem:

    button::-moz-focus-inner {
        border: 0 none;
        padding: 0;
    }
    
  • Wszyst­kie prze­glą­darki domyśl­nie wli­czają ramki i pad­ding do roz­mia­rów elementu:

    button {
        box-sizing: content-box;
        -moz-box-sizing: content-box;
        -ms-box-sizing: content-box;
        -webkit-box-sizing: content-box;
    }
    
  • IE7 i star­sze nie radzą sobie z auto­ma­tyczną sze­ro­ko­ścią przycisków:

    button {
        overflow: visible;
        width: 0;
    }
    button[type] {
        width: auto;
    }
    

¹ Doty­czy ogól­nie pro­duk­tów Mozilli.

Tymczasem w biurze…

Uru­cho­mi­li­śmy nową wer­sję ser­wisu Cen­trum Fak­tur!

Listę nowo­ści opi­sa­łem już w pod­lin­ko­wa­nym tek­ście, więc wła­śnie tam zapra­szam zarówno spra­gnio­nych wie­dzy, jak i miło­śni­ków mojej twór­czo­ści literackiej.

Kobiety i dary można pod­sy­łać do mnie. Pro­blemy, błędy, uwagi — tra­dy­cyj­nie, na Burzę Mózgów.

Python: Nie będziesz używał **kwargs nadaremnie

Pra­wi­dłowe użycie:

def foo(bar, *args, **kwargs):
    frobnicate(bar)
    baz(*args, **kwargs)

Nigdy tak¹:

def foo(bar, *args, **kwargs):
    frobnicate(bar)
    baz()

¹ Z wyjąt­kiem kilku API, które wyraź­nie rezer­wują sobie moż­li­wość wpro­wa­dze­nia dodat­ko­wych para­me­trów. Dzia­łają tak np. wbu­do­wane sygnały w Django.

Ciche poły­ka­nie śmieci w para­me­trach w malow­ni­czy spo­sób zemści się przy pierw­szej lite­rówce w kodzie. I będzie ci się należało.

Jeśli wydaje ci się, że w ten spo­sób skra­casz sobie kod, to przy­po­mnij sobie starą zasadę: utrzy­my­wa­nie kodu wymaga dwa razy wię­cej inte­li­gen­cji i sprytu, niż jego napi­sa­nie. Jeśli cały swój spryt wło­żysz w stwo­rze­nie kodu, to z defi­ni­cji jesteś za głupi, by go potem debugować.

To jest mój wątek i nie oddam go nikomu

Bez­sta­no­wość pro­to­kołu HTTP jest fak­tem. Nie­za­leż­nie od tego, czego chciałby autor danej apli­ka­cji. Na przy­kład nie jest prawdą, że jeden pro­ces lub wątek ser­wera jest przy­pi­sany jed­nemu, kon­kret­nemu odwie­dza­ją­cemu stronę. Wybacz­cie zatem łzy, które zakrę­ciły mi się w oczach na widok tego:

def _get_taxprocessor(request=None):
    taxprocessor = get_thread_variable('taxer', None)
    if not taxprocessor:
        if request:
            user = request.user
            if user.is_authenticated():
                user = user
            else:
                user = None
        else:
            user = get_current_user()

        taxprocessor = get_tax_processor(user=user)
        set_thread_variable('taxer', taxprocessor)

    return taxprocessor

Dalej był już tylko płacz:

def get_current_user():
    user = get_thread_variable('user', None)
    if user != None: return user
    req = get_current_request()
    if req == None: return None
    return req.user

I zgrzy­ta­nie zębów:

from threading import local

_threadlocals = local()

def set_current_user(user):
    set_thread_variable('user', user)

def set_thread_variable(key, var):
    setattr(_threadlocals, key, var)

def get_thread_variable(key, default=None):
    return getattr(_threadlocals, key, default)

def get_current_request():
    return get_thread_variable('request', None)
class ThreadLocalMiddleware(object):
    """Middleware that gets various objects from the
    request object and saves them in thread local storage."""

    def process_request(self, request):
        set_thread_variable('request', request)
        set_current_user(request.user)

Pytanie-zagadka: co się sta­nie z podat­kiem, gdy ten sam wątek ser­wera obsłuży innego użyt­kow­nika? Pyta­nie pomoc­ni­cze: skąd może pocho­dzić ów kod?

Otrzy­mu­jesz k3 Punkty Obłędu. Jeśli cał­ko­wita liczba zebra­nych punk­tów wynosi 6 lub wię­cej, roz­patrz test naby­cia trwa­łej cho­roby psy­chicz­nej zgod­nie z pro­ce­durą opi­saną w roz­dziale Obłęd podręcznika.

Być jak Satchmo

Z pew­no­ścią naczy­ta­łeś się już, jakie to Satchmo nie jest dosko­nałe, zazdro­ścisz i chciał­byś stwo­rzyć coś rów­nie wspa­nia­łego. Dość jed­nak nie­prze­spa­nych nocy, albo­wiem przy­go­to­wa­łem krótki porad­nik, który w kilku kro­kach pozwoli ci dorów­nać mistrzom.

Sięgaj tam, gdzie import nie sięga

Tak jest, zacznij od stwo­rze­nia modułu z myśl­ni­kiem w nazwie. Nie­stety, oczy­wi­sta nazwa email-auth.py została już zajęta — musisz się bar­dziej wysi­lić. Zna­jo­mym oczy wypadną z zazdro­ści, gdy tylko pierw­szy raz zobaczą:

hahaha = __import__('pokonalem-was', globals(), locals(), [], -1)

Uatrakcyjniaj pętle

Od dawna wia­domo już, że przed­wcze­sna opty­ma­li­za­cja jest złem, naszą odpo­wie­dzią będzie zatem przed­wcze­sna dez­op­ty­ma­li­za­cja! Oto przy­kład atrak­cyj­nego wyświe­tle­nia listy:

{% for product in products %}
    {% if forloop.first %} <ul>  {% endif %}
        <li>{% thumbnail product.main_image.picture 85x85 as image %}
        <a href="{{ product.get_absolute_url }}"><img src="{{ image }}" width="{{ image.width }}" height="{{ image.height }}" /></a>
        <a href="{{ product.get_absolute_url }}">{{ product.translated_name }}</a></li>
    {% if forloop.last %} </ul> {% endif %}
{% endfor %}

Tylko wyobraź sobie ich miny! Jeśli chcesz prze­sko­czyć mistrza, spró­buj prze­nieść kod do Pythona:

for counter, product in enumerate(products):
    if counter == 0:
        print '<ul>'
    # ...i tak dalej

Nie daj się zaszufladkować

Nie łudźmy się — prze­strze­nie nazw są dla fra­je­rów. Bez cere­gieli pakuj wszystko w ścieżkę Pythona i upew­nij się, że tak wła­śnie impor­tu­jesz swoje moduły. Pokaż, że jesteś waż­nia­kiem i twórz moduły o jak naj­ogól­niej­szych nazwach. Naucz fajan­sów pokory, tych kilka dodat­ko­wych wpi­sów w PYTHONPATH ich nie zabije. Mogą to zro­bić kon­flikty, ale jeśli chcą uży­wać cze­goś ponad twój fra­me­work, to sami są sobie winni i zasłu­żyli na karę.

export PYTHONPATH=~/web/satchmo/satchmo/apps

Unikaj biurokracji

Po co męczyć się z for­mu­la­rzami, gdy do wszyst­kiego się­gnąć można już w widoku? Nie­zwy­kle istotne jest tu uni­ka­nie request.REQUEST.

if request.method=="POST":
    reqdata = request.POST
else:
    reqdata = request.GET

if reqdata.has_key('quantity'):
    try:
        quantity = round_decimal(reqdata['quantity'], places=2, roundfactor=.25)
    except RoundedDecimalError:
        quantity = Decimal('1.0')
        log.warn("Could not parse a decimal from '%s', returning '1.0'", reqdata['quantity'])
else:
    quantity = Decimal('1.0')

Jeśli już musisz użyć for­mu­la­rza, upew­nij się, że upa­ku­jesz wszyst­kie, nie­zwią­zane ze sobą grupy pól w jed­nej dużej kla­sie. Dzięki temu zaosz­czę­dzisz sobie kilka wywo­łań is_valid() i jed­no­cze­śnie uda­rem­nisz wszel­kie próby innego wyko­rzy­sta­nia poszcze­gól­nych czę­ści przez te nie­do­ro­zwoje, które mają czel­ność impor­to­wać twoje klasy.

Wyznaczaj nowe trendy

Przez takich jak oni, pro­gra­mo­wa­nie obiek­towe stoi prak­tycz­nie w miej­scu. Pokaż im nowe sztuczki, takie jak zastą­pie­nie roz­sze­rza­nia klas nad­pi­sy­wa­niem funk­cji w locie¹:

def confirm_info(request, template='shop/checkout/protx/confirm.html', extra_context={}):
    payment_module = config_get_group('PAYMENT_PROTX')
    controller = confirm.ConfirmController(request, payment_module)
    controller.templates['CONFIRM'] = template
    controller.extra_context = extra_context
    controller.onForm = secure3d_form_handler
    controller.confirm()
    return controller.response

¹ W rze­czy­wi­sto­ści ConfirmController.onForm jest w kon­struk­to­rze klasy usta­wiany na ConfirmController._onForm, co można uznać za archi­tek­turę po dwa­kroć lepszą.

Parametry dobieraj z rozmachem

Piękno tkwi w róż­no­rod­no­ści. Upew­nij się zatem, że wyczer­piesz wszel­kie metody osią­gnię­cia tego samego celu.

class ProductImage(models.Model):
    # ...
    picture = ImageWithThumbnailField(verbose_name=_('Picture'),
        upload_to="__DYNAMIC__",
        name_field="_filename",
        max_length=200)
    # ...

class ImageWithThumbnailField(ImageField):
    # ...
    def __init__(self, verbose_name=None, name=None,
                 width_field=None, height_field=None,
                 auto_rename=NOTSET, name_field=None,
                 upload_to=upload_dir, **kwargs):
        # ...
        if upload_to == "__DYNAMIC__":
            upload_to = upload_dir
        # ...

Bądź elastyczny

Teraz twój sklep znaj­duje się w Pol­sce, ale kto wie, co będzie po obie­dzie? Upew­nij się, że cała kon­fi­gu­ra­cja może być edy­to­wana w locie. Zwłasz­cza te jej frag­menty, które wyma­gają restartu apli­ka­cji. To jedna z wielu sztu­czek, które zapew­nią ci popu­lar­ność w branży. Co prawda dawni przy­ja­ciele zazdrosz­czą ci już do tego stop­nia, że prze­stali się do cie­bie odzy­wać, ale i tak nie tęsk­nisz po tych pro­sta­kach. Od teraz twoim jedy­nym przy­ja­cie­lem jest apli­ka­cja django-livesettings. Na innych nie masz szans, bo przy­ja­ciół poznaje się w bie­dzie, a ty prze­cież wła­śnie zyska­łeś umie­jęt­no­ści, dzięki któ­rym prak­tycz­nie już jesteś bogaty.

Frogatto

Dziś prze­ko­na­łem się, że gry­walne wytwory open­so­urce nie koń­czą się na Bitwie o Wesnoth, NetHacku i Nexuiz (ten ostatni ma podobno tra­fić pod koniec roku na konsole)¹.

Zna­la­złem jed­nak Fro­gatto i opa­dła mi szczęka. Aż trudno uwie­rzyć, że coś tak pięk­nego powstało i ukrywa się pomię­dzy pięć­set dwu­na­stym rogu­elike i tysiąc piątą grą o epic­kich przy­go­dach pin­gwina Tuksa.

¹ War­sow, choć grą jest dobrą i wygląda ślicz­nie, nie ma otwar­tego kodu.

Frogatto

Fro­gatto można nazwać plat­for­mówką, jed­nak byłoby to okre­śle­nie bar­dzo krzyw­dzące. Co prawda ska­czemy po plat­for­mach by poły­kać kolej­nych prze­ciw­ni­ków i pluć nimi w stylu Kir­biego, lecz roz­grywka nie ogra­ni­cza się do nie­ustan­nego biegnięcia-w-prawo. Poja­wiają się walki z bossami, dźwi­gnie, zamknięte drzwi, klu­cze, ukryte czę­ści pozio­mów, a nawet całe dodat­kowe plan­sze. Jest też złoto, które zbie­ramy, by następ­nie wydać je w skle­pie na nowe umie­jęt­no­ści dla głów­nego boha­tera. W każ­dej chwili możemy rów­nież powró­cić do zwie­dzo­nej już czę­ści świata i jesz­cze raz prze­cze­sać zaka­marki w poszu­ki­wa­niu ukry­tych monet.

To wszystko ubrane jest w prze­śliczny pixel art, przy­wo­dzący na myśl Flashback i prace Henka Nie­borga oraz oprawę dźwię­kową żywcem wyjętą z cza­sów świet­no­ści szes­na­sto­bi­tow­ców. Jedyną wadą gry jest fakt, że nie star­cza jej na zbyt długo. Ser­decz­nie polecam.

O hakerach, miłości i sławie

Było cie­płe czerw­cowe popo­łu­dnie. Lubił te dni, gdy tempo pracy pozwa­lało na chwilę zapo­mnieć o wyście­ła­ją­cych biurko i sporą część pod­łogi papie­rach i z kub­kiem paru­ją­cej kawy w ręku wpa­try­wać się w powoli czer­wie­niące się na hory­zon­cie niebo. Ucie­kał wtedy myślami do swo­ich pla­nów, sta­ra­jąc się wyobra­zić sobie przy­szłość, kiedy nie będzie już musiał pra­co­wać. Marze­nie — wyrwać się z oko­wów pracy i być wol­nym jak ptak.

Zaraz jed­nak humor psuła mu prze­śla­du­jąca go nie­ustan­nie myśl. On tutaj, w biu­rze, z namasz­cze­niem trak­tuje każdą chwilę wytchnie­nia, która pozwala mu choć na chwilę odciąć się od sza­rego świata. W tym samym cza­sie te inży­nier­skie dar­mo­zjady mar­nują tyle wol­nego czasu by wymy­ślić lep­szą śrubę czy tward­szy metal. Na litość boską, kie­dyś ludziom wystar­czył kamień i kawa­łek patyka!

A zda­rza się, że ślęczą nad tym po pracy. „Mło­dzi są i w dupach się poprzew­ra­cało” ― lubił sobie tłu­ma­czyć, zaraz jed­nak dopa­dała go nie­zno­śna świa­do­mość, że i starsi kole­dzy coraz czę­ściej odda­wali się podob­nym, pozba­wio­nym sensu, prak­ty­kom. Co gor­sza, prze­bą­ki­wali przy tym coś o pasji i miło­ści. Czy pra­co­ho­lizm naprawdę zastą­pił u nich resztki rozsądku?

Z zadumy wyrwało go stu­ka­nie do drzwi. Dwa szyb­kie, chwila prze­rwy, znów dwa szyb­kie. Musi być, że listo­nosz. Jego cha­rak­te­ry­styczny spo­sób anon­so­wa­nia wła­snej obec­no­ści był tylko jed­nym z nawy­ków czło­wieka, o któ­rym przez wzgląd na grzecz­ność myślał jak o eks­cen­tryku. Nie można mu było jed­nak odmó­wić jed­nej zalety, kolejne por­cje danych dostar­cza codzien­nie, bez względu na pogodę.

Poże­gnał więc listo­no­sza i wró­cił do gabi­netu, by ponow­nie zająć cie­pły fotel przy ter­mi­nalu. Odsta­wił na bok sty­gnącą już kawę i stuk­nął lekko w jeden z kla­wi­szy. Mar­twy dotąd ekran roz­błysł cie­płą zie­le­nią. Nim jed­nak pozwo­lił dło­niom spo­cząć na kla­wia­tu­rze, ponow­nie się­gnął po kubek i delek­tu­jąc się łykiem cie­płego jesz­cze napoju, pozwo­lił swoim myślom błą­dzić jesz­cze przez chwilę.

Przy­po­mniał mu się przed­wczo­raj­szy tele­gram od żony. Jeśli kolej nie zawie­dzie, już w ten week­end ponow­nie weź­mie synka na kolana. Jak ten brzdąc szybko rośnie! Ani się czło­wiek obej­rzy, a sam zacznie palić w piecu. Mały wprost ten piec uwiel­bia i pierw­sze cie­płe dni wio­sny zawsze przyj­muje z nie­ma­łym rozczarowaniem.

I pomy­śleć, że te pijawki cheł­piły się myślą, że z meta­lo­wych rur zbu­dują coś od pieca lep­szego. Jesz­cze tro­chę i kom­pu­tery tymi swo­imi rurami będą skrę­cać. Zaję­liby się czymś poży­tecz­nym. Ale i ja nie powi­nie­nem mar­no­wać czasu.” ― się­gnął po przy­nie­sione przez pocz­towca zawi­niątko i wysu­płał z gazety paczkę pół­ca­lo­wych dys­kie­tek po cztery mega­bajty każda. Kon­tro­l­nie rzu­cił okiem na ety­kiety — „wypłaty, maj” — wszystko się zga­dzało. Wsu­nął pierw­szą w drzwiczki napędu i zasiadł do pracy.

– ❧ –

Tego samego wie­czoru posta­no­wił w końcu spi­sać swe myśli. Zakoń­czył więc Visi­Calc i uru­cho­mił Word­Star 7.2, z braku podob­nych pro­gra­mów nazy­wany zwy­kle „edy­to­rem”. Przez kolejną godzinę pokój wypeł­niały jedy­nie aro­mat kawy i stu­ka­nie kla­wi­szy, w jed­no­staj­nym ryt­mie prze­ku­wa­ją­cych kolejne for­mu­ło­wane w gło­wie postu­laty w zie­leń zapa­la­ją­cych się na ekra­nie liter.

Teraz zro­zu­mieją” ― pomy­ślał, gdy błysz­czący Epson MX-82, z gło­śnym jękiem prze­su­wa­ją­cych się igieł, wypluł z sie­bie pierw­szą stronę ode­zwy do kole­gów. Popra­wił oku­lary, oparł się o biurko swo­jego IBM-361 i z dumą odczytał:

O inżynierach, postępie i sławie

Alex, 8. czerwca roku pań­skiego 2023

Flattr

Flattr roz­prze­strze­nia się wiru­sowo. Ja swoje zapro­sze­nie dosta­łem od Deejay’a. Jesz­cze dwa można zdo­być u niego, trzy kolejne tutaj.

Jeśli zatem lubi­cie dzie­lić się pie­niędzmi (i wie­rzy­cie, że Flattr nie znik­nie razem z nimi), przy­łącz­cie się do tej jedy­nej w swoim rodzaju sieci wymiany napiw­ków. Po trzy zapro­sze­nia zgło­ście się w komen­ta­rzach — wyślę je na adres podany w formularzu.

Python: finanse

Nie dalej jak wczo­raj kolega pode­słał mi łatkę do mojej biblio­teki do słow­nego zapisu liczb i kwot. Nie zdra­dzę od kogo, by chro­nić nie­win­nego. Grunt, że łatka wyglą­dała tak:

--- to_words_pl.py	(upstream)
+++ to_words_pl.py	(working copy)
@@ -82,7 +82,7 @@
         iteration += 1
     if unit:
         result.append(unit)
-    result.append(u'%d/100' % int(remainder * 100))
+    result.append(u'%d/100' % int(round(remainder * 100)))
     result = ' '.join(result)
     return result

Zdzi­wi­łem się bar­dzo, bo zwy­kłem kwoty odpo­wied­nio zaokrą­glać do dwóch miejsc po prze­cinku. Co się jed­nak oka­zało? 0.48 zamie­niało się w 0.47. A dokład­niej? W 0.47999999999999998. Tuś mi, ptaszku.

Patrząc na 0.48 tak naprawdę w gło­wie widzia­łem decimal.Decimal('0.48'). Jak się jed­nak oka­zuje, nie­któ­rzy pró­bują ope­ra­cje finan­sowe prze­pro­wa­dzać na licz­bach zmien­no­po­zy­cyj­nych. Nie uży­wamy typu float do ope­ra­cji finan­so­wych. Dlaczego?

>>> 0.48
0.47999999999999998
>>> 0.82
0.81999999999999995

Do ope­ra­cji na licz­bach o zna­nej pre­cy­zji uży­wamy typu decimal.Decimal i jego kon­tro­lo­wa­nego (i kon­fi­gu­ro­wal­nego!) mecha­ni­zmu zaokrąglania:

>>> from decimal import Decimal
>>> Decimal('0.48') + Decimal('0.12')
Decimal('0.60')
>>> vat = Decimal('0.48') * Decimal('0.22')
>>> vat.quantize(Decimal('0.01'))
Decimal('0.11')
Creative Commons Attribution-NonCommercial-ShareAlike 2.5 Poland
This work by Patryk Zawadzki is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 Poland.