16

Sono relativamente nuovo a Python e non riesco a riconciliare le funzionalità del linguaggio con le abitudini che ho acquisito dal mio background in C++ e Java.Funzioni membro non membro vs Python

L'ultimo numero che sto avendo ha a che fare con l'incapsulamento, in particolare un'idea riassunto al meglio da Articolo 23 del Meyer "Effective C++":

Prefer non-member non-friend functions to member functions.

Ignorando la mancanza di un meccanismo friend per un istante, sono funzioni terzi ritenuto preferibile funzioni membro in Python, troppo?

Un obbligatoria, ad esempio asinino:

class Vector(object): 
    def __init__(self, dX, dY): 
     self.dX = dX 
     self.dY = dY 

    def __str__(self): 
     return "->(" + str(self.dX) + ", " + str(self.dY) + ")" 

    def scale(self, scalar): 
     self.dX *= scalar 
     self.dY *= scalar 

def scale(vector, scalar): 
    vector.dX *= scalar 
    vector.dY *= scalar 

Dato v = Vector(10, 20), ora possiamo chiamare sia v.scale(2) o scale(v, 2) di raddoppiare la grandezza del vettore.

Considerando il fatto che stiamo usando le proprietà in questo caso, quale delle due opzioni - se c'è - è meglio, e perché?

+2

Ritengo che questo semplicemente non sia vero in Python. Gli argomenti non si trovano in realtà con Python, dove è possibile modificare le classi così facilmente. Python si concentra anche sulla leggibilità, e sento che '' v.scale (2) '' è molto più chiaro di '' scale (v, 2) ''. Se si guarda nella libreria standard, tutte le funzioni più generali vengono mantenute come membri anziché come builtin. –

risposta

15

Interessante domanda.

Stai iniziando da un luogo diverso rispetto alla maggior parte delle domande provenienti dai programmatori Java, che tendono ad assumere che hai bisogno di lezioni quando non lo fai per lo più. Generalmente, in Python non ha senso avere classi, a meno che non si stia facendo specificamente incapsulamento dei dati.

Ovviamente, nel tuo esempio lo stai effettivamente facendo, quindi l'uso delle classi è giustificato. Personalmente, direi che dal momento che l'utente fa ha una classe, la funzione membro è il modo migliore per procedere: si sta facendo specificamente un'operazione su quella particolare istanza di vettore, quindi ha senso che la funzione sia una metodo sul vettore.

Dove si potrebbe desiderare di renderlo una funzione autonoma (non usiamo realmente la parola "membro" o "non membro") è se è necessario farlo funzionare con più classi che non ereditano necessariamente uno dall'altro o una base comune. Grazie alla digitazione anatra, è pratica abbastanza comune farlo: specifica che la tua funzione si aspetta un oggetto con un particolare insieme di attributi o metodi, e fai qualcosa con quelli.

+3

Mentre siamo qui, una nota in qualche modo correlata - una differenza dal C++ è che Python non ha alcun concetto di 'public' o' private'. La sintassi '_underscore' è il modo convenzionale per fornire un * suggerimento * che qualcosa è privato, ma non è * forzato *. –

2

Guarda il tuo esempio: la funzione non membro deve accedere ai membri dati della classe Vector. Questa non è una vittoria per l'incapsulamento. Questo è particolarmente importante in quanto cambia i membri dei dati dell'oggetto passato. In questo caso, potrebbe essere preferibile restituire un vettore ridimensionato e lasciare l'originale invariato.

Inoltre, non è possibile realizzare alcun beneficio del polimorfismo di classe utilizzando la funzione non membro. Ad esempio, in questo caso, può ancora far fronte solo a vettori di due componenti. Sarebbe meglio se facesse uso di una capacità di moltiplicazione dei vettori, o usasse un metodo per iterare sui componenti.

In sintesi:

  1. funzioni membro utilizzo di operare su oggetti di classi di controllare;
  2. utilizza funzioni non associate per eseguire operazioni puramente generiche implementate in termini di metodi e operatori che sono essi stessi polimorfici.
  3. Probabilmente è meglio mantenere la mutazione degli oggetti nei metodi.
+0

I membri a cui si accede (componente x e y) devono essere comunque pubblici affinché l'oggetto sia utile. Considerando i vettori con dimensioni diverse, hai comunque un punto sul polimorfismo. – delnan

+1

@delnan Non è che consideri la funzione standalone una violazione dell'involucro, piuttosto che ovviamente non migliori l'incapsulamento in alcun modo. Non disapprovo la sua mancanza di genericità - può solo far fronte a vettori di due elementi. Inoltre disapprovo il fatto che cambi i dati del vettore. – Marcin

+1

@Marcin - I nomi degli oggetti nell'esempio sono irrilevanti, e in effetti, a mia difesa, * era * etichettato come asinino. La domanda richiede commenti sui meriti relativi delle funzioni membro/non membro e * non * per suggerimenti su come rendere più generici esempi arbitrari. – JimmidyJoo

4

Una funzione gratuita offre la flessibilità di utilizzare anche la digitazione anatra per il primo parametro.

Una funzione membro offre l'espressività di associare la funzionalità alla classe.

Scegliere di conseguenza. Generalmente, le funzioni sono uguali, quindi dovrebbero avere tutte le stesse convinzioni sull'interfaccia di una classe. Una volta pubblicata una funzione gratuita scale, si sta effettivamente pubblicizzando che .dX e .dY fanno parte dell'interfaccia pubblica di Vector. Questo probabilmente non è quello che vuoi. Lo stai facendo in cambio della possibilità di riutilizzare la stessa funzione con altri oggetti che hanno uno .dX e .dY. Probabilmente non ti sarà prezioso. Quindi in questo caso preferirei sicuramente la funzione membro.

Per buoni esempi di preferire una funzione libera, abbiamo bisogno di guardare oltre la libreria standard: sorted è una funzione gratuita, e non una funzione membro di list, perché concettualmente si dovrebbe essere in grado di creare l'elenco che risulta dall'ordinamento di qualsiasi sequenza iterabile.

3

Preferisco funzioni non-amico terzi a funzioni membro

Si tratta di una filosofia di progettazione e può e deve essere estesa a tutti i linguaggi di programmazione OOP paradigma. Se ne capisci l'essenza, il concetto è chiaro

Se puoi fare a meno di richiedere l'accesso privato/protetto ai membri di una classe, il tuo progetto non ha un motivo per includere la funzione, un membro della classe . Pensare questo in Altro modo, quando si progetta una Classe, dopo aver enumerato tutte le proprietà, è necessario determinare il set minimo di comportamenti che sarebbe sufficiente per rendere la Classe. Qualsiasi funzione membro che è possibile scrivere utilizzando uno dei metodi pubblici disponibili/funzioni membro dovrebbe essere resa pubblica.

Quanto è questo applicabile in Python

In una certa misura, se si sta attenti. Python supporta un incapsulamento più debole rispetto agli altri linguaggi OOP (come Java/C++), in particolare perché non ci sono membri privati. (C'è una cosa chiamata Variabili private che un programmatore può facilmente scrivere prefiggendo un "_" prima del nome della variabile, che diventa privato della classe attraverso una funzione di manomissione del nome). Quindi, se adottassimo letteralmente la parola di Scott Meyer considerando che c'è una sottile differenza tra ciò che dovrebbe essere consultato dalla Classe e ciò che dovrebbe essere dall'esterno. Dovrebbe essere meglio lasciare al progettista/programmatore decidere se una funzione debba essere parte integrante della Classe o meno. Un principio disegno possiamo facilmente adottare, "Unless your function required to access any of the properties of the class you can make it a non-member function".

1

Come scale basa sulla moltiplicazione membro-saggio di un vettore, che attribuisce all'attuazione moltiplicazione come un metodo e definente scale essere più generale:

class Vector(object): 
    def __init__(self, dX, dY): 
     self._dX = dX 
     self._dY = dY 

    def __str__(self): 
     return "->(" + str(self._dX) + ", " + str(self._dY) + ")" 

    def __imul__(self, other): 
     if other is Vector: 
      self._dX *= other._dX 
      self._dY *= other._dY 
     else: 
      self._dX *= other 
      self._dY *= other 

     return self 

def scale(vector, scalar): 
    vector *= scalar 

Così, l'interfaccia di classe è ricca e ottimizzata mentre l'incapsulamento viene mantenuto.

+0

Penso che questo sia decisamente un approccio interessante al mio esempio specifico. – JimmidyJoo