Attribut de classe ou paramètre de méthode ?

a marqué ce sujet comme résolu.

Bonjour,

J'ai la classe suivante (ce que font les méthodes n'est pas important) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
"""Contain the class for smoothing data."""

from mpf import processors as proc
from mpf import settings as stg


__all__ = ('MovingAveraging')


class MovingAveraging:
    """Apply a moving average on data."""

    def __init__(self, step):
        self.step = step

    def save(self, cow, dates, prods):
        """Save the result of the analysis of ``cow``."""

        params = []

        for i in range(len(dates)):
            date = dates[i]
            prod = prods[i]

            q_select = 'SELECT id FROM CrudeData WHERE cow = ? AND date = ?'
            fid = stg.model.query(q_select, (cow, date))[0][0]

            params.append((fid, prod, self.step))

        q_insert = 'INSERT INTO SmoothedData VALUES (?, ?, ?)'
        stg.model.querymany(q_insert, params)

    def work(self, cow):
        """Run the analysis of ``cow``."""

        dates = stg.model.dates(cow)
        dates = proc.ma.truncate(dates, self.step)

        prods = stg.model.prods(cow)
        prods = proc.ma.smooth(prods, self.step)

        self.save(cow, dates, prods)

Et je l'instancie ici :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""The entry point of the code. Run it to make the analysis work."""

from mpf import analysis
from mpf import views
from mpf import settings as stg


def main():
    """Launch the analysis."""

    for cow in stg.model.cows():
        try:
            views.Crude().render(cow)

            analysis.MovingAveraging(step=2).work(cow)
            analysis.MovingAveraging(step=4).work(cow)
            views.MovingAveraging().render(cow, [2, 4])
        except sqlite3.IntegrityError:
            pass # TODO: cache


if __name__ == '__main__':
    main()

Je me pose alors plusieurs questions sur le fait d'utiliser des attributs de classe ou des paramètres pour les méthodes.

Tout d'abord, une des manières suivantes d'instancier mon objet, équivalentes au niveau du résultat, est-elle préférable ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Les paramètres sont passés en argument à la méthode
analysis.MovingAveraging().work(cow, step=2)
analysis.MovingAveraging().work(cow, step=4)

# Les paramètres sont passés à l'instanciation, puis ajoutés comme attributs de classe
analysis.MovingAveraging(cow, step=2).work()
analysis.MovingAveraging(cow, step=4).work()

# La méthode 'work' est appelée à l'instanciation
analysis.MovingAveraging(cow, step=2)
analysis.MovingAveraging(cow, step=4)

# Un tableau de pas est passé à l'instanciation
analysis.MovingAveraging(cow, steps=[2, 4])

# Un tableau de pas est passé en paramètres
analysis.MovingAveraging().work(cow, steps=[2, 4])

# Autres combinaisons...

La deuxième question porte sur le passage de valeurs entre les méthodes. Actuellement, je fais :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def work(self, cow):
    """Run the analysis of ``cow``."""

    dates = stg.model.dates(cow)
    dates = proc.ma.truncate(dates, self.step)

    prods = stg.model.prods(cow)
    prods = proc.ma.smooth(prods, self.step)

    self.save(cow, dates, prods)

Mais je pourrais aussi faire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def work(self):
    """Run the analysis."""

    self.dates = stg.model.dates(self.cow)
    self.dates = proc.ma.truncate(dates, self.step)

    self.prods = stg.model.prods(self.cow)
    self.prods = proc.ma.smooth(prods, self.step)

    self.save()

Existe-t-il, de manière générale, une règle pour répondre à ces questions ?

Merci. :)

+0 -0

Pour ton soucis de classe, la réponse que je donnerai, ça serait :

  • "Est-ce qu'une fonction peut le faire de manière simple ?", si oui le problème est réglé.

  • "Est-ce que ta classe a besoin de ces données plus tard ?", si non, tu n'as probablement pas besoin de les stocker en attribut.

  • "Est-ce que ton objet a une sémantique d'unité, associée à un seul jeu de donnée ?", si oui, tu peux problablement vouloir que ta classe garde ces données d'une manière ou d'une autre, pour faire d'autres opérations dessus.

  • "Rien ne correspond ?", quel est le rôle de ta classe ?

sans pour autant que ce soit des règles figées, c'est plus mes propres règles.

+1 -0
  • "Est-ce qu'une fonction peut le faire de manière simple ?", si oui le problème est réglé.

C'est probablement le cas ici. Seulement, passer par une classe me permettra de factoriser mon code via l'héritage, non ?

  • "Est-ce que ta classe a besoin de ces données plus tard ?", si non, tu n'as probablement pas besoin de les stocker en attribut.

Même si, comme dans le lien précédent, ça m'évite de passer des tonnes d'arguments à mes fonctions ?

  • "Est-ce que ton objet a une sémantique d'unité, associée à un seul jeu de donnée ?", si oui, tu peux problablement vouloir que ta classe garde ces données d'une manière ou d'une autre, pour faire d'autres opérations dessus.

En l'occurrence, je pense que oui, vu qu'il s'agit de la production lissée. Mais je stocke ça en base, donc je peux le retrouver quand je veux. ^^

Merci !

+0 -0

D'un point de vue personnel, si je devais faire une classe, j'aurai plutot eu tendance à offrir la même interface pour l'accès aux données et l'accès aux données lissées, peut être avec une évaluation fainéante+forcée, et pouvoir donner un générateur comme source de donnée. Mais je ne me suis pas beaucoup penché sur les besoins.

1
2
3
4
5
m = MovingAverage(source=bddsourcewrapper(step=2)) # ici c'est pour l'exemple
print(m[i]) # évaluation du i-ème terme
m.compute(all=True)
# OU
m.compute(range(10,100,2))

Par contre, il m'est plus logique de penser que si l'on fait une moyenne, c'est que l'on a déjà les données, et que l'on cherche à analyser le signal correspondant, donc la transformation directe et immédiate par une fonction me parait plus KISS, et le tri en fonction de l'étape/la gestion de la BDD/l'affichage/la sauvegarde du résultat peut être laissée à un autre bout de code suffisamment compétent pour ça (parce qu'ici tu as des données figées, comme le nom de ta table, ou le fait d'utiliser une base de donnée, dans une classe qui dit simplement être une moyenne mobile, donc niveau factorisation, argument de fonction ou pas, c'est plutot moyen).

Ouep, je dispose des données en base et je cherche à les analyser. Pour le calcul brut, j'ai des fonctions ici. là, ma classe MovingAverage se charge de toute l'analyse de la vache en question : chargement des données, application de la moyenne mobile (simple appel au processor correspondant) puis sauvegarde (qui me pose d'ailleurs des problèmes).

Donc je suis d'accord que le calcul brut se résume à une fonction, mais pour l'analyse entière, j'aurai sûrement moyen de factoriser ça.

Non ? ^^

+0 -0

Là encore, ça ne tient qu'à ma maigre expérience, et sûrement une bonne déformation due au caml, mais je ne vois pas beaucoup d'utilité de faire une classe qui est utilisée de cette façon :

1
2
3
4
5
    # début du scope
    obj = Object(param)
    obj.work(data)
    obj.save()
    # fin du scope et del obj

Certes, tu as moins de "données" à passer en paramètre à chaque fois, mais même pour ça tu peux faire la même chose avec des fonctions d'ordre supérieur, et si c'est pour que ce soit ton main, tu as plus intérêt à mettre directement le code dans le main, ça en fait moins et c'est tout autant factorisable, voire bien plus qu'avec une classe.

1
2
3
4
5
6
7
8
9
def cow_process(params): 
    def func(data,post_trait_func):
        #....
        return post_trait_func(data)
    return func

f = cow_process(params, sauver_donnees_dans_bdd)
traite = f(data)
#faire autre chose avec les données

L'exemple là encore est bidon, mais ça montre que factorisation != classe, et l'intérêt de faire une classe pour l'analyse de la vache, c'est seulement si elle interagit avec d'autres objets de manière directe (c'est elle qui les utilise pour changer son comportement) ou indirecte (c'est elle qui est utilisée), c'est-à-dire qu'elle fournit la base et que tu clipses les legos dessus (accès aux données, accès au moyen de sauvegarde, méthode de traitement, le tout de manière assez opaque). Ici, le nom de "moyenne mobile" pour la classe n'est pas du tout représentative de ce qu'elle fait.

L'intérêt d'utiliser une classe est très souvent lié au fait qu'on crée (instancie) plusieurs objets du même type avec des attributs dont il est fort possible qu'ils soient différents…

On peut aussi l'utiliser, mais c'est rare, dans des cas où on souhaite éviter les variables globales et où l'on souhaite absolument que ces variables n'appartiennent qu'à la classe concernée.

Il n'y a pas de règles pour coder, il suffit juste d'être clair dans ta tête et dans l'organisation…

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte