Python - Modo corretto per chiamare una funzione all'interno di una classe basata su un determinato argomento passato

1

Fammi illustrare con un esempio.

Dire che voglio creare una classe Returns che generi i ritorni di uno stock, per esempio. I ritorni possono essere aritmetici o logaritmici e voglio essere in grado di scegliere l'istanza che voglio. Per il momento, faccio quanto segue:

def arithmetic(prices):
    daily_ret = []
    for i rows in prices:
        returns.append(prices(i)-prices(i-1))
    return daily_ret

def logarithmic(prices):
    daily_ret = []
    for i rows in prices:
        returns.append(np.ln(prices(i) / prices(i-1))
    return daily_ret

class Returns:
    def __init__(self, stock_ticker, return_calc=arithmetic):
        self.stock_ticker = stock_ticker
        self.return_calc = return_calc

    def calculate_returns(self):
        get_price_data_from_ticker = whatever_data_source()
        if self.return_calc = 'arithmetic':
            returns = arithmetic(get_price_data_from_ticker)
        elif self.return_calc = 'logarithmic':
            returns = logarithmic(get_price_data_from_ticker)
        return returns

x = Returns('AAPL US Equity', return_calc='logarithmic')
print(x.calculate_returns())

Sembra che il codice sia scritto male, e mi manca qualcosa di ovvio perché ho sempre avuto la stessa domanda.

Un'altra cosa che ho fatto è usare getattr per ottenere in modo dinamico il nome della funzione o della classe che userò e trasmetterò gli argomenti. Un'altra cosa che ho trovato è usare globals () per creare dinamicamente le classi.

Mi chiedo se ci sia una macro costruzione che mi manca, perché anche se queste 2 soluzioni sopra sembrano piuttosto belle, sembra che ci sia un problema strutturale più profondo con il codice.

La lingua è Python 3

    
posta elefant 24.10.2018 - 00:12
fonte

3 risposte

1

Penso che tu sia sulla buona strada; ma l'utilizzo di un'iniezione di dipendenza potrebbe funzionare meglio nel tuo caso. Potresti fare qualcosa di simile:

def arithmetic(prices):
    daily_ret = []
    for i rows in prices:
        returns.append(prices(i)-prices(i-1))
    return daily_ret

def logarithmic(prices):
    daily_ret = []
    for i rows in prices:
        returns.append(np.ln(prices(i) / prices(i-1))
    return daily_ret

class Returns:
    def __init__(self, stock_ticker, return_calc=arithmetic):
        self.stock_ticker = stock_ticker
        self.return_calc = return_calc

    def calculate_returns(self):
        get_price_data_from_ticker = whatever_data_source()
        return self.return_calc(get_price_data_from_ticker)

x = Returns('AAPL US Equity', return_calc=logarithmic)
print(x.calculate_returns())

passando direttamente la funzione invece di passare una costante di stringa per identificare quale funzione usare.

A proposito, questo è quello che stavi facendo in questa riga:

def __init__(self, stock_ticker, return_calc=arithmetic):

che significa che il valore predefinito che stai trasmettendo in questo momento non è una stringa, ma direttamente la funzione

    
risposta data 24.10.2018 - 11:30
fonte
2

In questo caso, penso che usare diverse classi sia probabilmente la strada da percorrere.

from abc import ABC, abstractmethod

class Returns(ABC):
    def __init__(self, stock_ticker):
        self.stock_ticker = stock_ticker
    def get_price_data_from_ticker(self):
        pass # FIXME: implement getting stuff from the data source here
    @abstractmethod
    def calculate_returns(self, prices):
        pass
    def get_returns(self):
        return self.calculate_returns(self.get_price_data_from_ticker())

class ArithmeticReturns(Returns):
    def calculate_returns(self, prices):
        return [prices(i) - prices(i - 1) for i in prices]

class LogarithmicReturns(Returns):
    def calculate_returns(self, prices):
        return [np.ln(prices(i) / prices(i - 1)) for i in prices]

x = LogarithmicReturns('AAPL US Equity')
print(x.get_returns())

Concetti usati:

  • Classi di base astratte: Returns è ora una classe che non può essere dimostrata, solo le sottoclassi che implementano tutti i metodi astratti possono. Ciò significa che entrambe le sottoclassi implementano la stessa interfaccia senza dover fare tutto il lavoro pesante stesso.
  • Comprensione delle liste: l = [a + 1 for a in exp] è equivalente a

    l = []
    for a in exp:
        l.append(a + 1)
    
  • Separazione dei dubbi: calculate_returns non è più interessato al modo in cui i dati vengono raccolti dal ticker, ma calcola semplicemente i ritorni in base ai prezzi assegnati.

Il tuo codice conteneva errori di sintassi in arithmetic e logarithmic nei loop, quindi dovrai correggerlo. Se prices è una lista, userei qualcosa come [current_price - previous_price for current_price, previous_price in zip(prices[1:], prices)] , ma ho deciso di lasciarlo come è nel caso in cui prices sia in realtà uno strano iterabile che itera sugli indici e viene indicizzato chiamando.

    
risposta data 24.10.2018 - 12:28
fonte
0

Un metodo più semplice consiste nell'includere entrambe le funzioni come metodi di classe, ma assegnare il metodo visibile nel costruttore, come

self.calculate_return = self.logarithmic if .... else self.arithmetic

Quindi il chiamante userà sempre la funzione corretta senza fare un test e una chiamata di metodo più profonda ogni volta. Puoi farlo anche con sottoclassi, ma questo è più semplice, anche se un po 'spaventoso per i codificatori non python.

    
risposta data 24.10.2018 - 04:39
fonte

Leggi altre domande sui tag