Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Transformer une classe en objet

0 votes

Je vais chercher à bien expliquer mon problème de manière concrète.

Je souhaite créer des jeux vidéos avec le module pygame. Je me suis rendu compte que de nombreux morceaux de code et variables revenait quelques soit le jeu que je faisait. D'ou l'idée de faire un module.

Dans ce module, j'ai créé une classe Game. C'est ma classe static qui contient toutes les variables importante. Ses variables, je les stocks en tant qu'attribut de classe de Game. Une de mes variables est la variable Couleurfondecran.
Pour bien exprimer, j'ai ça :

class Game :
    Ecran = pygame.display.set_mode((1600, 900))
    Couleur_fond_ecran = (0, 0, 0)
    # autres variables importantes …

    @staticmethod
    def afficher_fond_ecran() :
        Game.Ecran.fill(Game.Couleur_fond_ecran)

Je vais créer mon jeux en dehors du module.
Je souhaite rajouter des variables utilisable partout dans mon jeu.
Donc j'ai besoin d'une classe équivalente à Game, mais pour mon jeu.
Pour bien exprimé, j'ai besoin de ça :

class MonJeu :
    Couleur_fond_ecran = (0, 0, 0)
    soldats = [] # Ma liste de soldats.
    # autres variables importantes.

    @staticmethod
    def afficher_fond_ecran() :
        MonJeu.Ecran.fill(MonJeu.Couleur_fond_ecran)

Mon idée, c’était de faire hériter MonJeu par Game pour pouvoir accéder à tout.

class MonJeu(Game) :
    soldats = []
    # Je supprime tout le reste déjà présent dans Game.

Mais, si dans mon fichier main du jeu, je fait MonJeu.couleurfondecran = (255, 255, 255), comme la méthode afficherfondecran est faite par Game, la couleurfondecran resteras (0, 0, 0).

Dans ce cas ci, je peux contourner le problème en mettant dans mon main Game.couleurfondecran = (255, 255, 255). Mais ça me pose également des problèmes dans les collisions et les choses comme ça que je ne sait pas encore résoudre.

J'aimerais également si possible que Game reste "invisible" lorsque je travail sur mon jeu. (n'utiliser que MonJeu) Ceci afin de faciliter l'utilisation de ce module.

Ainsi, comment puis-je modifier les variables de Game en utilisant sa classe fille MonJeu ? Lié, en quelques sortes, les variables de MonJeu et de Game ?

demandé 19-Sep-2016 par Andy (314 points)
edité 24-Sep-2016 par Andy

D'après ce que je peux comprendre de tes explications (le pourquoi tu veux faire ça), j'ai l'impression que le problème se situe dans le fait que tu utilises des variables et des classes statiques.

Pourquoi est-ce que tu n'utilises pas des instances ?

Par exemple:

class Mother(object):
    def __init__(self):
        self.mon_attribut = "default value"

    def get_mon_attribut(self):
        return self.mon_attribut

class Child(Mother):
    def __init__(self):
        super().__init__()
        self.mon_attribut = "specific value"

>>> m = Mother()
>>> c = Child()
>>> print(m.get_mon_attribut())
default value
>>> print(c.get_mon_attribut())
specific value

Comme tu peux le voir, la méthode get_mon_attribut() est définie dans la classe mère, mais elle peut s'utiliser avec la classe fille (la classe fille hérite aussi des méthodes).

Pourquoi veux-tu absolument passer par des variables statiques ? Je n'ai pas l'impression que ça ait beaucoup de sens ici :-/

Je te rappelle que tu peux toujours modifier les attributs d'une classe importée d'un autre module, ces changement seront vu par tout le monde, y compris le code du module d'origine.

ça ne me semble pas pratique du tout de faire ça. Comment es-ce que je rajoute des méthodes par exemple ? Alors qu'avec l'héritage j'ai ma classe dans mon dossier du projet, je peux lui rajouter des attributs, des méthodes ... et je ne m'occupe plus du tout de la classe static du module.

  • Si, tu t'occupes de la classe static du module puisque tu veux quelle change. Il serait d'ailleurs intéressant de savoir pourquoi ces changement sont importants pour toi. Par exemple, si tu vois ClasseStatic comme un sac rempli de variables globales, ton problème se situe peut-être dans le terme "variable globale".

  • 'ClasseStatic.renvoie42 = lambda self: 42' est une manière de rajouter une méthode à ClasseStatic après sa création. Ce bout de code peut se trouver n'importe où, même en dehors du module d'origine.

  • Dans ta vision actuelle, est-ce que tu comptes n'utiliser que les classes ClasseStatic et ClasseFille ou bien aussi des instances de ces classes ?

Effectivement, je ne suis pas sur à propos des termes que j'utilise.

Ok, je vois, ça peut être intéressant ce que tu me propose.
J'ai réedité ma question pour que vous puissiez tous mieux comprendre mon problème.

Et non, je ne compte pas utiliser des instances de ses classes.

@frzk
Avec des instances, ça me semble devenir très compliqué et ça me bloque pas mal. J'ai réédité la question pour que vous puissiez bien comprendre mon problème.

@Andy
Oui, c'est plus clair maintenant.

Et non, je ne compte pas utiliser des instances de ses classes.
...
Avec des instances, ça me semble devenir très compliqué et ça me bloque pas mal.

Je posterais une réponse quand j'aurais le temps (et si quelqu'un ne le fait pas avant), mais c'est pourtant comme ça que tu vas résoudre ton problème.

Tu n'as qu'à voir la quantité de discussions et d'incompréhension que ton approche suscite alors que ton problème d'organisation de code n'est pas si étrange. Cela devrait au moins te poser des questions.

5 Réponses

+1 vote
 
Meilleure réponse

Voici un code qui ne fait pas exactement ce que tu veux, mais ça s'en rapproche (comparer les deux cas). Attention, c'est un hack, je ne pense pas que j'utiliserais ça dans la vraie vie (c'est sans doute ton approche du problème qu'il faut revoir).

class Meta(type):    
    def __setattr__(self, name, value):
        if name not in self.__dict__:
            for cls in self.__mro__:
                if name in cls.__dict__:
                    setattr(cls, name, value)
                    return
        super().__setattr__(name, value)


class A(metaclass=Meta):
    x = "ok"

A.y = 0

class B(A):
    x = "dac"

B.y = 42

print(A.x, B.x)  # ok dac
print(A.y, B.y)  # 42 42

A la réflexion, c'est assez casse gueule, parce que l'ordre des déclarations modifie le comportement. Si par exemple tu fais B.y = 42 avant A.y = 0, ça ne donnera pas la même chose.

répondu 22-Sep-2016 par yoch (2,066 points)
sélectionné 12-Oct-2016 par Andy

Et du coup, ça rejoint la solution de bubulle en commentaire plus haut...

Voilà qui répond à :

  • "transformer une classe en objet" (pas besoin, ce sont par essence des objets de type 'type')

  • "faire que la classe parent subisse aussi les changements de la classe enfant" (via un __setattr__ adapté au bon endroit)

Il ne reste plus qu'à trouver pourquoi on voudrait faire une chose aussi casse geule, reformuler le problème général correctement, et enfin y répondre.

Ne marche pas sur python 3.5.2 semble-t-il. Le deuxième print me donne "0 0"

Tres étrange, j'ai testé sur python 3.5.1 et j'ai bien 42 42. Et puis je ne vois vraiment pas pourquoi ce serait 0 0.

La flemme d'héberger les screenshots, mais sur ipython j'obtiens 0 0, alors que depuis l'interpréteur j'obtiens bien 42 42.

Qu'es-ce que signifie un hack en python ?
La solution me semble plutôt bien.

0 votes

[EDIT]

Ok j'ai peut-être trouvé quelque chose en passant par les méthodes de classe. Dis-moi si ça te va :

class Parent():
    mon_attribut = "lol"

    @classmethod
    def change(cls, val):
        cls.mon_attribut = val

class Enfant(Parent):
    def __init__(self, nouvelle_valeur):
        Parent.change(nouvelle_valeur)

e = Enfant("yolo")
Parent.mon_attribut # 'yolo'
j = Enfant("Encore une fois ? ")
Parent.mon_attribut # "Encore une fois ?"

Et le petit article pour illustrer le bouzin : https://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods

Après, ce n'est pas parfait puisque tu as une dépendance forte, vu que tu es forcé de renseigner le nom de la classe parent à chaque fois

Concernant la modification d'attributs propres à la classe parente, ça me paraît compliqué, vu que tu n'opères pas sur la classe directement, mais sur ses instances.

Pour transformer la classe en objet, un petit tour sur stackoverflow semble révéler un truc intéressant, dans la standard lib, les Abstract Based Classes (collections.abc) :

http://stackoverflow.com/questions/21361106/how-would-i-implement-a-dict-with-abstract-base-classes-in-python

répondu 20-Sep-2016 par betepoilue (144 points)
edité 20-Sep-2016 par betepoilue

J'ai du mal à comprendre en quoi une classe abstraite pourrait me servir.
Ou alors tu n'as peut être pas bien compris mon problème.
Je l'ai mieux expliqué dans mes autres commentaires si tu souhaite encore comprendre.

Transformer une classe en objet, ça ne veut rien dire, c'est déjà le cas...

Hum, je vois. ça peut être bien, mais pour le moment ça me semble très compliqué et "lourd" en nombre de ligne pour le code.
Par contre, je peux peut être utiliser ça pour créer un mécanisme automatique qui change les valeurs de la classe mère à chaque fois que je change les valeurs de la classe fille ?
Il me semble que Sam&Max parle d'une tel fonction spéciale.

Pourquoi le downvote ? C'est la solution la plus courte en LOC, la plus pythonique à ton problème. Vous avez tous un problème sur ce forum sérieux

@betepoilue
Un downvote non expliqué est effectivement peu constructif et, je te comprends, agaçant.

Personnellement, je n'ai (up/down)voté aucune des contributions sur la question car je ne la comprends toujours pas, difficile donc d'apprécier les réponses même celles (dont la tienne) qui sont claires, illustrées et référencées.

Le -1, c'est moi et je l'ai explique plus haut. Il y a d'ailleurs d'autres imprécisions dans ton post, mais la flemme de tout relever.

@yoch, j'ai certes mal édité le post mais d'une part mais en attendant la solution proposée est la plus simple.

+1 vote

Je pense qu'il y a pas mal de confusion dans ton esprit sur certaines notions.

Quand tu écris :

class ClasseStatic:
    mon_attribut = "ok"
    def __init__(self):
        pass

mon_attribut est une variable de classe. Et cette variable n'est pas vraiment statique.

Cela signifie notamment que :

>>> mon_instance = ClasseStatic()
>>> print("{}".format(mon_instance.mon_attribut))
ok
>>> print("{}".format(ClasseStatic.mon_attribut))
ok

La variable de classe est à la fois accessible via une instance de classe (mon_instance.mon_attribut) et aussi via la classe (ClasseStatic.mon_attribut).

Mais aussi (et surtout) que :

>>> mon_instance.mon_attribut = "plop"
>>> print(mon_instance.mon_attribut)
plop
>>> print(ClasseStatic.mon_attribut)
ok

Et aussi que :

>>> ClasseStatic.mon_attribut = "dac"
>>> ClasseStatic.mon_attribut
dac
>>> mon_instance.mon_attribut
plop

Lorsque tu hérites de cette classe, le comportement est le même. Ta classe fille hérite bien de mon_attribut en premier lieu :

>>> class ClasseFille(ClasseStatic):
...    def __init__(self):
...        super().__init__()
>>>
>>> ClasseFille.mon_attribut
ok
>>> ClasseFille.mon_attribut is ClasseStatic.mon_attribut
True

Et tu peux effectivement écraser cette variable en lui donnant une autre valeur (ce que tu fais en lui attribuant la valeur "dac". Mais ça reste une variable de classe, donc liée à ta classe ClasseFille (et uniquement à cette classe).

>>> ClasseFille.mon_attribut is ClasseStatic.mon_attribut
True
>>> ClasseFille.mon_attribut = '{}'.format(ClasseStatic.mon_attribut)
>>> print(ClasseStatic.mon_attribut)
ok
>>> print(ClasseFille.mon_attribut)
ok
>>> ClasseStatic.mon_attribut is ClasseFille.mon_attribut
False

Assigner une valeur à une variable de classe ne change pas la valeur de la variable de classe de la classe mère. L'héritage ne va pas dans ce sens, il va uniquement dans le sens Mère --> Fille.

répondu 20-Sep-2016 par Frzk (120 points)

Effectivement, je pense avoir une confusion surtout avec ce que j'appel Classe statique.
Mais je connais tout ce que tu viens de décrire.

Et tu peux effectivement écraser cette variable en lui donnant une autre valeur (ce que tu fais en lui attribuant la valeur "dac". Mais ça reste une variable de classe, donc liée à ta classe ClasseFille (et uniquement à cette classe).

C'est justement ça mon problème. C'est que je cherche à faire une classe héritant de ClasseStatic en faisant en sorte que les variables de classe soit les mêmes et soit lié (est la même ID).

0 votes

En passant par une métaclasse tu peux t'en sortir. Mais cette solution ne marche que pour les modifications d'attributs des instances :

class ClasseStatic(object):
    mon_attribut = "ok"

def monsetattr(self, name, value):
    print('monattr')
    for baseklass in self.__class__.__bases__:
        if hasattr(baseklass, name):
            print('[{}] set {} to {}'.format(baseklass, name, value))
            setattr(baseklass, name, value)
    super(ClasseStatic, self).__setattr__(name, value)

class ClasseFilleMeta(type):
    def __new__(cls, name, bases, adict):
        adict['__setattr__'] = monsetattr
        return type.__new__(cls, name, bases, adict)

class ClasseFille(ClasseStatic):
    __metaclass__ = ClasseFilleMeta
    mon_attribut = "dac"

print(ClasseFille.mon_attribut)
print(ClasseStatic.mon_attribut)
c = ClasseFille()
c.mon_attribut = 'dac'
print(c.mon_attribut)
print(ClasseFille.mon_attribut)
print(ClasseStatic.mon_attribut)
répondu 21-Sep-2016 par frodon1 (108 points)

Pas besoin de metaclasse pour faire marcher ton idée, de l'héritage multiple suffit.

Tu crées une classe InvasiveSetAttr qui implémente le __setattr__ que tu proposes et tu définies ClasseFille qui dérive de ClasseStatic et de InvasiveSetAttr.

@bubulle: non, ne marchera pas sur une définition de classe, mais seulement sur les instances.

Chez moi, ton exemple ne semble pas fonctionner. Effectivement, j'obtiens :


print(ClasseFille.mon_attribut) dac print(ClasseStatic.mon_attribut) ok c = ClasseFille()
c.monattribut = 'dac'
print(c.mon
attribut) dac print(ClasseFille.mon_attribut) dac print(ClasseStatic.mon_attribut) ok

Ici, ClasseStatic.monattribut et ClasseFille.monattribut sont différent.
Or, je souhaite que ClasseStatic.mon_attribut soit "lié" à ClasseFille.

Mais je sens qu'il y a une solution par la.
Si je comprend bien, il y a une méthode pour modifier automatiquement les attributs de la classe static par la classe fille ?

Tu crées une classe InvasiveSetAttr qui implémente le setattr que tu proposes et tu définies ClasseFille qui dérive de ClasseStatic et de InvasiveSetAttr.
@bubulle: non, ne marchera pas sur une définition de classe, mais seulement sur les instances.

class ClasseStatic:
    attribut = "ok"
    
class ClasseFille(ClasseStatic):
    def __setattr__(self, name, value):
        print("good")

ClasseFille.attribut = "dac" # Rien ne se passe.
a = ClasseFille()
a.attribut = "dac"
# good

Effectivement, ce n'est que sur les instances.
Dommage, je crois que sa aurait été exactement ce dont j'aurai eu besoin.

@yoch: voilà ce que je voulais dire. après test, les deux marchent pareil.

class ClasseStatic(object):
    mon_attribut = "ok"

class InvasiveSetAttr(object):
    def __setattr__(self, name, value):
        print('monattr')
        for baseklass in self.__class__.__bases__:
            if hasattr(baseklass, name):
                print('[{}] set {} to {}'.format(baseklass, name, value))
                setattr(baseklass, name, value)
        super(InvasiveSetAttr, self).__setattr__(name, value)

class ClasseFille(InvasiveSetAttr, ClasseStatic):
    mon_attribut = "dac"

@bubulle: c'est possible que ça marche pareil (je n'ai pas bien regarde la metaclasse en question, je pensais qu'elle résolvais le problème du PO), mais ça ne répond pas au problème initial, dans la mesure ou avec ton code:

>>> ClasseStatic.mon_attribut
'ok'
0 votes

Suite à la reformulation du problème par l'OP le 02/10/2016

Dans ce cas, il te faut une méthode de classe :

class Game :
    Ecran = pygame.display.set_mode((1600, 900))
    Couleur_fond_ecran = (0, 0, 0)
    # autres variables importantes …

    @classmethod
    def afficher_fond_ecran(cls) :
        cls.Ecran.fill(cls.Couleur_fond_ecran)

class MonJeu(Game):
    # ...

MonJeu.Couleur_fond_ecran = (255, 255, 255)

>>> MonJeu.afficher_fond_ecran()
(255, 255, 255)

>>> Game.afficher_fond_ecran()
(0, 0, 0)

Les méthodes de classe reçoivent en premier paramètre la classe (et non pas une instance comme avec self). Ce paramètre est, par convention, nommé cls.

Note que les méthodes de classe peuvent être appelées directement par la classe (ou une classe héritante) mais aussi par une instance de ces classes (même si ça ne t'intéresse pas).

répondu 4-Oct-2016 par Frzk (120 points)
edité 4-Oct-2016 par Frzk
...