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.
