Qual è il punto dei moduli?
I moduli sono un meccanismo per strutturare un'applicazione in parti distinte e riutilizzabili. Per esempio. un programma di utilità della riga di comando potrebbe calcolare il codice che analizza l'argomento in un modulo separato che può essere utilizzato anche in altri strumenti da riga di comando. Poiché un modulo ha un'interfaccia pubblica chiara e può nascondere i dettagli di implementazione all'interno del modulo, anche l'applicazione complessiva diventa meno strettamente accoppiata.
Posso scambiare un modulo con un altro modulo con la stessa interfaccia?
Non necessariamente, e non è questo il punto dei moduli. Tuttavia, ci sono varie tecniche per implementare i moduli. Uno consiste nel definire una struttura di puntatori di funzioni che definiscono il tipo dell'intero modulo. Quando un modulo viene caricato, fornisce un'istanza di questo modulo. Questa tecnica viene spesso utilizzata per simulare i moduli in JavaScript:
// a mathModule may be anything that supplies "add" and "sub" functions
var mathModule = (function () {
var privateVar = "secret";
function add(a, b) { return a + b; }
function sub(a, b) { return a - b; }
function privateFunction() { return "very" + privateVar; }
// return an “object” (Python: dict) that defines the public interface
return {
add: add,
sub: sub,
};
})();
Questa tecnica può anche essere adattata a C per fornire moduli reali e può essere facilmente tradotta in Python. Tuttavia, nessuna delle due lingue ha lambda e C non ha alcuna chiusura, quindi l'esempio tradotto sarebbe più limitato.
Dato che qui abbiamo a che fare con i puntatori di funzione, abbiamo un livello importante di indirezione. In linea di principio, potremmo avere più istanze della stessa interfaccia modulo in una volta e possiamo facilmente passare a un'implementazione diversa se offre la stessa interfaccia.
Questo suona molto simile alla programmazione orientata agli oggetti, ma mancano alcuni dettagli importanti: per OOP, la struttura dell'istanza deve contenere anche dati oltre ai metodi e deve passare la struttura dell'istanza come argomento indietro in qualsiasi metodo che è stato invocato. Discuto creando un sistema di oggetti JavaScript fuori dalle chiusure in un post del blog in modo più approfondito.
I moduli di Python funzionano così?
Tipo di. In effetti, i moduli di Python si comportano molto come un oggetto normale, tranne per il fatto che non abbiamo un argomento self
. Tuttavia, Python manca la parte in cui definiamo effettivamente un'interfaccia chiara per il nostro modulo. Laddove l'esempio di JavaScript sopra riportato può esplicitamente scegliere se esporre una funzione attraverso l'interfaccia pubblica, Python rende accessibile qualsiasi simbolo nel file scope quando import
ing in un modulo. Ci sono trucchi che limitano questo problema (come la definizione delle funzioni nei moduli helper e quindi l'importazione selettiva dell'interfaccia pubblica nel __init__.py
del modulo), ma in Python finiamo per definire l'interfaccia attraverso la nostra documentazione, non attraverso il codice.
Quindi, per farla breve, Python ha spazi dei nomi che chiama "moduli", ma non offre alcuna incapsulazione.
Quindi, come posso ereditare un modulo?
Il concetto di "ereditarietà" non ha senso quando si discute dei moduli, poiché si tratta più di un termine OOP. Lì, " Subclass
eredita da Base
" significa (a) che Subclass
è un sottotipo di Base
e (b) che le istanze di Subclass
possono in qualche modo utilizzare l'implementazione definita da Base
. Come saprai, riutilizzare l'implementazione può avvenire tramite due meccanismi: l'ereditarietà dell'implementazione (in cui il meccanismo di ricerca del metodo per Subclass
viene inizializzato con i metodi per Base
) e la composizione (dove Subclass
delega a Base
esempio).
Con la codifica module-as-dict, questo può essere illustrato come tale:
# The original module.
# Can be instantiated like 'mathModule = mathModule_load()'
def mathModule_load():
private_var = "secret"
def add(a, b): return a + b
def sub(a, b): return a - b
def private_function(): return "very" + private_var
return {
'add': add,
'sub': sub,
}
# Inherit the mathModule via composition
# Overrides "add" to type-check for int arguments
def checkedMathModule_load():
base = mathModule_load() # the module instance we inherit
def add(a, b):
if type(a) is not int or type(b) is not int:
raise TypeError("give me ints!")
return base['add'](a, b)
def sub(a, b):
return base['sub'](a, b)
# return our own module instance that meets the expected interface
return {
'add': add,
'sub': sub,
}
# Inherit the mathModule by changing the module definition
# Overrides "add" to emit debug info
def chattyMathModule_load():
module = mathModule_load() # the module instance we inherit
original_add = module['add']
def my_add(a, b):
print("DEBUG: add(%d, %d)" % (a, b))
return original_add(a, b)
module['add'] = my_add
return module
OK, possiamo farlo ora con i moduli integrati?
* brontolone * Python può essere formalmente multi-paradigma, ma chiaramente favorisce le soluzioni OOP a problemi come questo. Possiamo rielaborare l'esempio precedente per usare i moduli built-in di Python, ma non è carino. Nota che i moduli di Python non hanno un concetto paragonabile di "istanze", poiché i moduli sono globali.
Invece dell'esplicita dict
che memorizza la funzione del modulo, useremo la normale sintassi di Python per aggiungere simboli all'ambito del file.
I moduli sono organizzati nella seguente gerarchia di file:
mathModule/
__init__.py
detail.py
checkedMathModule/
__init__.py
detail.py
chattyMathModule/
__init__.py
detail.py
mathModule/__init__.py
:
from .detail import add, sub
del detail
mathModule/detail.py
:
private_var = "secret"
def add(a, b):
return a + b
def sub(a, b):
return a - b
def private_function():
return "very" + private_var
checkedMathModule/__init__.py
:
# this module offers the same interface as mathModule
from .detail import add, sub
del detail
checkedMathModule/detail.py
:
import mathModule as base
def add(a, b):
if type(a) is not int or type(b) is not int:
raise TypeError("give me ints!")
return base.add(a, b)
def sub(a, b):
return base.sub(a, b)
chattyMathModule/__init__.py
:
from mathModule import *
# now import the function we want to override
from .detail import my_add as add
del detail
chattyMathModule/detail.py
:
from mathModule import add as original_add
def my_add(a, b):
print("DEBUG: add(%d, %d)" % (a, b))
return original_add(a, b)
Non sono sicuro che del detail
non romperà nulla, ma senza di esso gli utenti potrebbero fare mathModule.detail.private_function()
e ottenere qualcosa 'verysecret'
, che interrompe completamente l'incapsulamento.
Queste tecniche sono un po 'di lavoro, ma sono molti sforzi inutili per qualcosa che potrebbe essere fatto molto più facilmente con oggetti regolari. Inoltre, Python è speciale perché i moduli sono rappresentati come oggetti modulo che possono essere passati intorno a come valori , mentre il sistema dei moduli della maggior parte delle lingue è molto più statico. Queste tecniche non sono rappresentative della programmazione modulare, ma piuttosto di un "Voglio fare OOP senza utilizzare le parti OOP del mio linguaggio", una mentalità che non è costruttiva per nulla se non comprendere meglio OOP.