# 6. Module

Dacă, atunci când lucrați în terminal, părăsiți interpretorul Python-ului, veți constata, la revenirea la promptul interpretorului, că toate definițiile scrise în linia de comandă (fie ele definiții de funcții ori de variabile) s-au pierdut. Din acest motiv, atunci când vă pregătiți să scrieți un program mai lung de câteva linii de cod, ar fi mai bine să folosiți un editor de text pentru prepararea acestui program și apoi să rulați programul folosind fișierul în cauză pe post de date de intrare în interpretor. O asemenea întreprindere este numită creare de script. Pe măsură ce programul dumneavoastră se lungește, veți dori să-l împărțiți în mai multe fișiere pentru a-l întreține mai ușor. De asemeni, veți ajunge în situația de a dori să întrebuințați o funcție bine scrisă chiar de dumneavoastră în cadrul unor programe diverse fără să-i copiați, de fiecare dată, codul în vreunul din programele respective.

Pentru a vă ajuta în această privință, Python-ul dispune de o modalitate de a introduce definiții într-un fișier și apoi de a le utiliza fie într-un script fie într-o instanță interactivă a interpretorului. Un astfel de fișier este numit modul; definițiile dintr-un modul pot fi importate atât în alte module cât și în modulul main (modulul principal, adică acea colecție de variabile la care aveți acces dintr-un script executat la nivelul cel mai înalt precum și atunci când lucrați în modul calculator – bucla REPL).

Modulul constituie, așadar, un fișier conținând definiții și instrucțiuni scrise în Python. Numele fișierului este numele modulului la care adăugăm sufixul `.py`. În interiorul (codului Python al) unui modul, numele modulului (sub forma unui șir de caractere) ne va fi accesibil ca valoare a variabilei globale `__name__`. Ca să vedem aceasta, vă rugăm să întrebuințați editorul dumneavoastră favorit de text pentru a crea fișierul cu numele `fibo.py`, situat în directorul curent și având următorul conținut:

    # un modul cu numerele lui Fibonacci

    def fib(n):
        """Afișează termenii șirului lui Fibonacci de până la n."""
        a, b = 0, 1
        while a < n:
            print(a, end=' ')
            a, b = b, a+b
        print()

    def fib2(n):
        """Returnează termenii șirului lui Fibonacci de până la n."""
        rezultat = []
        a, b = 0, 1
        while a < n:
            rezultat.append(a)
            a, b = b, a+b
        return rezultat

Apoi, porniți interpretorul de Python și importați modulul tocmai scris cu comanda care urmează:

    >>> import fibo

Execuția comenzii nu va adăuga numele funcțiilor definite în `fibo` direct la spațiul de nume curent (vezi Domenii de valabilitate și spații de nume în Python pentru mai multe detalii); tot ce va face va fi să adauge numele `fibo` spațiului de nume respectiv. Apelând la numele modulului, veți putea accesa funcțiile acestuia:

    >>> fibo.fib(1000)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
    >>> fibo.fib2(100)
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
    >>> fibo.__name__
    'fibo'

Dacă intenționați să folosiți o anumită funcție în mod frecvent, atunci îi puteți atribui unei variabile locale numele acesteia:

    >>> fib = fibo.fib
    >>> fib(500)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

## 6.1. Mai multe despre module

Orice modul poate conține atât instrucțiuni executabile cât și definiții de funcții. Aceste instrucțiuni sunt folosite la inițializarea modulului. Adică, ele vor fi executate doar prima dată când numele modulului în cauză va fi întâlnit de către interpretor într-o instrucțiune de import. [1] (Instrucțiunile vor fi rulate și atunci când fișierul – modulul – va fi executat ca script.)

Fiecare modul are propriul său spațiu de nume privat, care va fi utilizat ca spațiu de nume global de către toate funcțiile definite în modulul respectiv. Astfel, autorul unui modul poate întrebuința variabile globale în codul Python al modulului fără să se teamă de eventualele conflicte de nume dintre aceste variabile și variabilele globale ale utilizatorului (modulului). Pe de altă parte, dacă sunteți siguri pe ceea ce faceți, atunci veți putea întrebuința variabilele globale ale unui modul cu aceeași notație pe care o utilizați pentru a vă referi la funcțiile modulului, și anume `nume_de_modul.nume_de_item`.

Modulele pot importa alte module. Se obișnuiește, fără a fi obligatoriu, să plasăm toate instrucțiunile `import` la începutul oricărui modul (respectiv, al oricărui script). Numele de module importate, dacă sunt poziționate la nivelul cel mai de sus al modulului (adică, în exteriorul oricărui cod de funcție sau de clasă din modul), vor fi adăugate spațiului de nume global al modulului.

Există o variantă a instrucțiunii `import` care realizează importurile de nume dintr-un modul direct în spațiul de nume al modulului importator. Iată un exemplu de folosire a ei:

    >>> from fibo import fib, fib2
    >>> fib(500)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Varianta nu va introduce numele modulului din care facem importurile în spațiul de nume local (așa că, în exemplul anterior, `fibo` nu este definit).

Dispunem chiar și de o modalitate de a importa toate numele pe care le definește un modul:

    >>> from fibo import *
    >>> fib(500)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Această variantă a instrucțiunii despre care discutăm va importa toate numele cu excepția celor care încep cu o bară jos (`_`). De cele mai multe ori, programatorii de Python nu vor apela la această facilitate a limbajului deoarece ea va introduce un set necunoscut de nume în spațiul de nume folosit de interpretor, putând astfel masca o serie de chestiuni deja definite de dumneavoastră.

Să remarcăm, așadar, că obiceiul de a importa `*` fie dintr-un modul fie dintr-un pachet este descurajat de profesioniști, dat fiind că el cauzează, în majoritatea situațiilor, apariția unui cod greu de citit. Cu toate acestea, este permis să îl practicați în sesiunile interactive de lucru cu interpretorul de Python, ca să aveți mai puțin de tastat.

Dacă numele unui modul este urmat de `as`, atunci numele situat la dreapta lui `as` va fi legat direct de modulul importat.

    >>> import fibo as fib
    >>> fib.fib(500)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Procedând astfel, importul modulului se va realiza la fel ca în urma execuției instrucțiunii `import fibo`, singura deosebire fiind aceea că modulul va fi disponibil sub numele (modificat convenabil) de `fib`.

Efecte similare vor fi obținute dacă procedăm în acest mod și atunci când folosim versiunea cu `from` a instrucțiunii de import:

    >>> from fibo import fib as fibonacci
    >>> fibonacci(500)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Notă

Din rațiuni de eficiență, orice modul va fi importat o singură dată într-o sesiune de lucru cu interpretorul. Așa că, dacă îi aduceți modificări modulului, va trebui să reporniți interpretorul – ori, dacă este vorba doar despre un singur modul ce trebuie testat interactiv, folosiți `importlib.reload()`, precum, de exemplu, în `import importlib; importlib.reload(numele_modulului)`.

### 6.1.1. Executând module ca scripturi

Atunci când rulați un modul Python cu

    python fibo.py <argumente>

codul modulului va fi executat ca și cum modulul ar fi fost importat, cu excepția faptului că variabila `__name__` va fi setată ca `"__main__"`. Ceea ce înseamnă că, dacă ați adăuga următoarele linii de cod la sfârșitul modulului dumneavoastră:

    if __name__ == "__main__":
        import sys
        fib(int(sys.argv[1]))

atunci ați face fișierul (modulului) utilizabil atât ca script cât și ca modul importabil doarece codul în care se parsează conținutul liniei de comandă va rula numai atunci când modulul se execută ca fișier „main”:

    $ python fibo.py 50
    0 1 1 2 3 5 8 13 21 34

În schimb, atunci când modulul este importat, codul nu va fi rulat:

    >>> import fibo
    >>>

Acest procedeu este frecvent întrebuințat fie pentru a-i adăuga modulului o interfață cu utilizatorul convenabilă fie în scop de testare (atunci când, la rularea modulului ca script, va fi executată o suită de teste).

### 6.1.2. Calea de căutare a modulelor

Atunci când un modul numit `cutare` este importat, interpretorul va începe prin a căuta un modul predefinit cu același nume. Numele acestor module sunt înșiruite în tuplul `sys.builtin_module_names`. Dacă nu îl găsește, atunci interpretorul va căuta un fișier intitulat `cutare.py` într-o listă de directoare furnizată de variabila `sys.path`. `sys.path` este inițializată cu una din următoarele locații:

- Directorul care conține scriptul folosit ca dată de intrare la interpretor (sau directorul curent dacă nu a fost precizat niciun fișier).

- `PYTHONPATH` (o listă cu nume de directoare, având aceeași sintaxă ca variabila interpretorului de comenzi – de la englezescul shell – `PATH`).

- Locația implicită, depinzând de modalitatea de instalare (incluzând, în mod convențional, un director `site-packages`, manipulată de modulul `site`).

Mai multe detalii se găsesc în Inițializarea căii de acces din modulul sys.path.

Notă

În sistemele de fișiere care suportă legături slabe (în englezește, ca jargon, symlink), directorul care conține scriptul de intrare va fi calculat numai după ce este urmată legătura. Cu alte cuvinte, directorul care conține legătura slabă nu îi va fi adăugat căii de căutare a modulelor.

Odată inițializate, programele Python pot modifica `sys.path`-ul. Directorul care conține scriptul ce este rulat va fi plasat la începutul căii de căutare, înaintea căii bibliotecii standard. Ceea ce înseamnă că scripturile din directorul în cauză vor fi încărcate în locul modulelor cu același nume din directorul bibliotecii standard. Dacă aceasta nu este intenția dumneavoastră, atunci situația constituie o eroare serioasă. A se vedea Module standard pentru mai multe informații.

### 6.1.3. Fișiere Python „compilate”

Pentru a accelera încărcarea modulelor, Python-ul salvează în directorul de cache `__pycache__` versiuni compilate ale tuturor modulelor, cu numele `modulul.versiunea.pyc`, în care versiunea conține codificarea formatului pentru fișierul compilat; în general, ea include numărul de versiune al Python-ului. De exemplu, în ediția (de la englezescul release) 3.3 a CPython-ului, varianta compilată a lui cutare.py ar fi salvată drept `__pycache__/cutare.cpython-33.pyc`. O atare convenție de nume le permite modulelor compilate ale diverselor ediții și diferitelor versiuni ale Python-ului să coexiste.

Python-ul compară data modificării fișierelor-sursă cu cea a versiunilor compilate pentru a stabili dacă acestea din urmă sunt depășite cronologic și este nevoie să fie recompilate. Procesul de comparare și recompilare este complet automatizat. În plus, modulele compilate sunt independente de platforma de calcul, astfel că aceeași bibliotecă poate fi partajată (de la englezescul shared) cu sisteme de arhitecturi diferite.

Python-ul nu verifică zona de cache în două circumstanțe. În prima, pentru că el recompilează de fiecare dată, fără a stoca rezultatul recompilării în directorul de cache, modulul care se încarcă din linia de comandă. În cea de-a doua situație, el nu verifică zona de cache atunci când modulul nu dispune de fișier-sursă. Pentru ca Python-ul să suporte o distribuție fără cod-sursă (adică una doar compilată), modulul compilat trebuie să fie localizat în directorul-sursă și nu trebuie să existe în distribuție niciun fișier-sursă al modulului în cauză.

Ponturi pentru experți:

- Puteți întrebuința opțiunile `-O` sau `-OO` ale unei comenzi Python pentru a reduce mărimea unui modul compilat. Opțiunea `-O` elimină instrucțiunile de aserțiune (de la englezescul assert), iar opțiunea `-OO` elimină atât instrucțiunile de aserțiune cât și șirurile __doc__. Dat fiind că anumite programe se bazează pe existența acestora, ar trebui să utilizați această opțiune numai atunci când sunteți sigur pe ceea ce faceți. Modulele „optimizate” au eticheta `opt-` și sunt, de obicei, mai mici (decât originalele). Edițiile viitoare ale Python-ului pot modifica efectele acestor optimizări.

- Niciun program nu va rula mai repede atunci când este citit dintr-un fișier `.pyc` decât atunci când este citit dintr-un fișier `.py`; tot ceea ce este mai rapid în privința fișierelor `.pyc` este viteza cu care sunt încărcate.

- Modulul `compileall` le poate crea fișiere .pyc tuturor modulelor dintr-un director.

- Mult mai multe detalii despre acest proces al compilării modulelor, inclusiv un arbore de decizie util, se găsesc în PEP 3147.

## 6.2. Module standard

Python-ul vine însoțit de o bibliotecă de module standard, descrisă într-un document separat, Referința bibliotecii Python (denumit „Referința bibliotecii” de aici înainte). Anumite module sunt construite ca parte a interpretorului; ele oferă acces la operații care nu fac parte din corpul (de la englezescul core) limbajului dar sunt, cu toate acestea, incluse în el, fie din rațiuni de eficiență fie pentru a-i da acces utilizatorului la primitive ale sistemului de operare precum apelurile (de) sistem. Setul acestor module este accesibil printr-o opțiune de configurare care depinde, la rândul ei, de platforma de calcul folosită. De exemplu, modulul `winreg` va fi disponibil numai pe sistemele de calcul cu Windows. Un modul particular merită atenția dumneavoastră: `sys`, el fiind construit ca parte a oricărui interpretor de Python. Variabilele `sys.ps1` și `sys.ps2` au drept valori șirurile de caractere folosite pe post de promptul primar, respectiv de promptul secundar:

    >>> import sys
    >>> sys.ps1
    '>>> '
    >>> sys.ps2
    '... '
    >>> sys.ps1 = 'C> '
    C> print('Văleu!')
    Văleu!
    C>

Aceste două variabile sunt definite doar dacă interpretorul lucrează în modul interactiv.

Variabila `sys.path` este o listă formată din șiruri de caractere care determină calea de căutare a modulelor. Este inițializată cu o cale de acces implicită preluată din variabila de mediu `PYTHONPATH`, ori dintr-o altă cale de acces prestabilită atunci când `PYTHONPATH` nu a fost setată. O puteți modifica folosind operațiile tipice cu liste:

    >>> import sys
    >>> sys.path.append('/ufs/guido/lib/python')

## 6.3. Funcția `dir()`

Funcția predefinită `dir()` este întrebuințată la aflarea numelor pe care le definește un anumit modul. Ea returnează o listă sortată de șiruri de caractere:

    >>> import fibo, sys
    >>> dir(fibo)
    ['__name__', 'fib', 'fib2']
    >>> dir(sys)
    ['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
     '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
     '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
     '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
     '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
     'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
     'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
     'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
     'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
     'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
     'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
     'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
     'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
     'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
     'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
     'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
     'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
     'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
     'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
     'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
     'warnoptions']

Utilizată fără argumente, `dir()` va lista numele definite în sesiunea curentă:

    >>> a = [1, 2, 3, 4, 5]
    >>> import fibo
    >>> fib = fibo.fib
    >>> dir()
    ['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

Remarcați faptul că ea listează toate tipurile de nume: pe cele de variabile, pe cele de module, de funcții șamd.

`dir()` nu listează nici numele funcțiilor predefinite (în englezește, ca jargon, built-in) și nici pe cele ale variabilelor predefinite. Dacă vă interesează lista acestora, atunci aflați că ele sunt definite în modulul standard `builtins`:

    >>> import builtins
    >>> dir(builtins)
    ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
     'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
     'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
     'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
     'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
     'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
     'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
     'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
     'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
     'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
     'NotImplementedError', 'OSError', 'OverflowError',
     'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
     'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
     'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
     'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
     'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
     'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
     'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
     '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
     'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
     'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
     'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
     'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
     'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
     'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
     'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
     'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
     'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
     'zip']

## 6.4. Pachete

Pachetele constituie o modalitate de structurare a spațiului de nume al modulelor Python folosind „nume-cu-puncte de module” (de la englezescul dotted). De exemplu, numele de modul `A.B` se referă la (sub)modulul `B` din pachetul numit `A`. Tot așa cum utilizarea modulelor îl scapă pe autorul unui modul de grija eventualelor conflicte de nume cu variabilele globale din modulul altui autor, întrebuințarea de nume-cu-puncte de module îi scapă pe autorii unor pachete multi-modul precum NumPy sau Pillow de grija numelor de module ale altor autori de pachete multi-modul.

Să presupunem că doriți să proiectați o colecție de module (adică, un „pachet”) dedicate manipulării sistematice a fișierelor de sunet și a datelor audio. Deoarece există numeroase formate de fișier de sunet (recunoscute, de obicei, după extensiile lor, cum ar fi: `.wav`, `.aiff`, `.au`), veți avea de construit și de întreținut o colecție crescândă de module care să se ocupe cu conversia între diversele formate de fișier. De asemeni, există felurite operații pe care veți dori să le aplicați unor date audio (precum mixaje, adăugarea de ecouri, aplicarea unor funcții de egalizare, crearea unor efecte stereo artificiale), astfel că, pe lângă cele de dinainte, veți scrie în permanență noi module pentru aceste operații. Iată o structură posibilă a pachetului dumneavoastră (exprimată în stilul unui sistem ierarhic de fișiere – în englezește, ca jargon, filesystem):

    sunet/                       Pachetul-rădăcină
          __init__.py            Inițializarea pachetului de sunet
          formate/               Subpachetul conversiilor de formate de fișier
                  __init__.py
                  citesc_wav.py
                  scriu_wav.py
                  citesc_aiff.py
                  scriu_aiff.py
                  citesc_au.py
                  scriu_au.py
                  ...
          efecte/                 Subpachetul efectelor audio
                  __init__.py
                  ecou.py
                  înconjurător.py
                  inversare.py
                  ...
          filtre/                 Subpachetul filtrelor
                  __init__.py
                  egalizator.py
                  codare_vo.py
                  karaoke.py
                  ...

Atunci când importăm un pachet, Python-ul va căuta subdirectorul pachetului în lista de directoare din `sys.path`.

Prezența unui fișier `__init__.py` într-un director obligă Python-ul să trateze respectivul director drept pachet (un astfel de fișier lipsește dacă folosim directorul pe post de pachet spațiu de nume, adică o caracteristică oarecum avansată a Python-ului despre care nu discutăm chiar acum). Acest procedeu face ca directoare cu nume banale, cum ar fi `string`, să nu poată conține, din pură întâmplare, module de interes ce ar trebui să fie găsite abia la locații ulterioare din calea de căutare a modulelor. În cel mai simplu caz, `__init__.py` este doar un fișier gol, însă el poate include, în general, cod de inițializare a pachetului ori o setare a variabilei `__all__`, la care ne vom referi mai târziu.

Utilizatorii unui pachet pot importa module individuale din acesta, ca de exemplu:

    import sunet.efecte.ecou

Această instrucțiune încarcă submodulul `sunet.efecte.ecou`. Ne vom referi la el numai folosindu-i numele complet.

    sunet.efecte.ecou.filtru_de_ecou(intrare, ieșire, întârziere=0.7, atenuare=4)

Alt fel de a importa submodulul anterior este dat de:

    from sunet.efecte import ecou

Și această instrucțiune încarcă submodulul `ecou`, pe care îl face accesibil fără prefixul cu nume de pachete, deci ne permite să-l întrebuințăm după cum urmează:

    ecou.filtru_de_ecou(intrare, ieșire, întârziere=0.7, atenuare=4)

Încă o variație a modului de a importa este să importăm direct fie funcția fie variabila dorită:

    from sunet.efecte.ecou import filtru_de_ecou

Ca și până acum, instrucțiunea va încărca submodulul `ecou` însă ne va permite să folosim funcția `filtru_de_ecou()` din acesta numai cu numele ei:

    filtru_de_ecou(intrare, ieșire, întârziere=0.7, atenuare=4)

Să remarcăm că, atunci când executăm instrucțiunea `from pachet import element`, elementul poate fi sau un submodul (ori un subpachet) al pachetului sau orice alt nume definit în pachet, adică o funcție, o clasă ori o variabilă. Instrucțiunea `import` testează mai întâi dacă elementul este definit în pachet; dacă nu, atunci va presupune că elementul este numele unui modul și va încerca să-l încarce. În cazul în care presupusul modul nu va fi găsit, va fi ridicată o excepție `ImportError`.

În schimb, atunci când facem uz de o sintaxă de felul `import element.sub_element.sub_sub_element`, fiecare (sub)element, cu excepția ultimului, trebuie să fie nume de pachet; ultimul element poate fi sau nume de modul sau nume de pachet însă nu și numele vreunei clase, funcții ori variabile definite în elementul precedent.

### 6.4.1. Importând * dintr-un pachet

Ce se întâmplă însă atunci când utilizatorul folosește instrucțiunea `from sunet.efecte import *`? În mod ideal, putem spera că interpretorul se duce la sistemul de fișiere, găsește toate (sub)modulele prezente în pachet și le importă. O atare activitate este de așteptat să dureze mult iar importul de sub-module poate avea și efecte secundare nedorite pe care nu le-am produce dacă importul unui sub-modul oarecare ar fi realizat în mod explicit.

Singurul mod în care putem evita situațiile neplăcute este ca autorul pachetului să ofere un index explicit al acestuia. Instrucțiunea `import` folosește următoarea convenție: atunci când codul-sursă al fișierului `__init__.py` dintr-un pachet definește o variabilă de tip listă numită `__all__`, valoarea acestei variabile va fi folosită drept lista numelor de module ce vor trebui importate atunci când interpretorul va avea de executat instrucțiunea `from pachet import *`. Actualizarea listei de nume, cu ocazia lansării unei noi versiuni a pachetului, rămâne numai la latitudinea autorului acestuia. Autorii de pachete pot decide chiar și să nu întrebuințeze această facilitate atunci când nu consideră că ar fi de vreun folos să se importe * din pachetele lor. Ca exemplu, să presupunem că fișierul `sunet/efecte/__init__.py` ar conține următoarea linie de cod:

    __all__ = ["ecou", "înconjurător", "inversare"]

Aceasta ar însemna că, la execuția lui `from sunet.efecte import *`, vor fi importate cele trei submodule mai sus numite ale pachetului `sunet.efecte`.

Băgați de seamă că numele de (sub)module pot fi acoperite (de la englezescul shadowed; adică, au devenit invizibile pentru instrucțiunea curentă) de nume definite local. De exemplu, dacă ați adăugat definiția unei funcții `inversare` la codul-sursă al fișierului `sunet/efecte/__init__.py`, atunci execuția lui `from sunet.efecte import *` va produce importul celor două submodule `ecou` și `înconjurător` însă nu și importul submodulului `inversare` deoarece numele acestuia este acoperit de cel al funcției `inversare` definită local:

    __all__ = [
        "ecou",          # se referă la fișierul 'ecou.py'
        "înconjurător",  # se referă la fișierul 'înconjurător.py'
        "inversare",     # !!! se referă la funcția 'inversare' !!!
    ]

    def inversare(mesaj: str):  # <-- acest nume îl acoperă pe cel al submodulului 'inversare.py'
        return mesaj[::-1]    #       dacă se execută 'from sunet.efecte import *'

Atunci când `__all__` nu este definită, execuția instrucțiunii `from sunet.efecte import *` nu va produce importul tuturor submodulelor din pachetul `sunet.efecte` în spațiul de nume curent; tot ceea ce va face este să se asigure mai întâi că pachetul `sunet.efecte` a fost importat (ceea ce poate însemna inclusiv execuția codului de inițializare găsit în `__init__.py`), după care va importa toate numele definite în pachet. Aceasta va include numele (precum și submodulele încărcate explicit) din `__init__.py`. Va include, desigur, și toate submodulele pachetului care au fost deja încărcate explicit prin execuția precedentelor instrucțiuni `import`. Să luăm în considerare următoarele linii de cod:

    import sunet.efecte.ecou
    import sunet.efecte.înconjurător
    from sunet.efecte import *

În exemplul de față, modulele `ecou` și `înconjurător` sunt importate în spațiul de nume curent deoarece ele sunt definite în pachetul `sunet.efecte` la momentul execuției instrucțiunii `from...import`. (Ceea ce rămâne valabil și în cazul când `__all__` este definită.)

Cu toate că anumite module au fost proiectate să nu exporte decât acele nume care respectă șabloane precise atunci când dumneavoastră veți folosi instrucțiunea `import *` în raport de ele, întrebuințarea acestei instrucțiuni în codul destinat producției se socotește a fi o practică greșită.

Țineți minte, nu este nimic în neregulă dacă utilizați instrucțiunea `from pachet import submodulul_cutare`! De fapt, aceasta constituie chiar formularea recomandată a oricărei instrucțiuni de import, cu excepția situației în care modulul importator are de folosit submodule omonime din pachete diferite.

### 6.4.2. Referințe intra-pachet

Atunci când pachetele sunt organizate în subpachete (cum este cazul pachetului `sunet` din exemplul de mai sus), puteți folosi importurile absolute pentru a vă referi la submodule din (sub)pachete înrudite. Astfel, dacă presupunem că modulul `sunet.filtre.codare_vo` trebuie să utilizeze modulul `ecou` din pachetul `sunet.efecte`, atunci putem întrebuința instrucțiunea `from sunet.efecte import ecou`.

Puteți construi și instrucțiuni de import relativ, scrise sub forma `from modul import nume`. Aceste instrucțiuni de import vor folosi puncte de sine stătătoare pentru a indica fie pachetul curent fie pachetul-părinte al pachetului curent la care se referă importul relativ în cauză. Dacă scrieți cod situat, de exemplu, în modulul `înconjurător`, atunci ați putea utiliza formulele:

    from . import ecou
    from .. import formate
    from ..filtre import egalizator

Să observăm că importurile relative se raportează la numele pachetului din care face parte modululul curent. Dat fiind că modulul principal (`"__main__"`) nu face parte din niciun pachet, modulele pe care intenționăm să le utilizăm pe post de modul principal al vreunei aplicații Python trebuie să folosească doar importuri absolute.

### 6.4.3. Pachete situate în mai multe directoare

Pachetele dispun de încă un atribut special, și anume de `__path__`. El este inițializat cu o valoare de tipul secvență de șiruri de caractere care include numele directorului în care s-a aflat fișierul `__init__.py` al unui anumit pachet înainte să înceapă execuția codului Python din fișierul respectiv. Valoarea acestui atribut poate fi modificată. Modificarea ei va afecta căutările de module și de subpachete ale pachetului în discuție.

Deși este o caracteristică de care avem nevoie rar, o putem folosi cu succes la lărgirea setului de module conținute într-un pachet.

Note de subsol

[ 1 ]

De fapt, și definițiile de funcții sunt «instrucțiuni» care se «execută»; execuția unei definiții de funcție de la nivel de modul va adăuga numele acestei funcții la spațiul de nume global al modulului respectiv.