IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Système de script souple avec eval()


Sujet :

Python

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Février 2011
    Messages
    51
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2011
    Messages : 51
    Par défaut Système de script souple avec eval()
    Bonsoir,

    J'aimerai réaliser et utiliser un système de scripts qui aurait le comportement suivant :
    Charger dans un dict tout les fichiers contenus dans un dossier contenant chacun une réimplémentation d'une classe.

    Je ne sais pas si cela veut dire quelque-chose ou pas, mais je pense que des exemples seront beaucoup plus parlants à vous comme à moi :

    Voici la classe abstraite AbstractBlock qui comporte peut d’éléments (c'est juste pour l'exemple) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class AbstractBlock:
        def __init__(self):
            pass
     
        def onCross(self, vehicle):
            pass
    Et voici par exemple un fichier "grass_radioactive.block" :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class GrassRadioactiveBlock(AbstractBlock):
        def __init__(self):
            pass
     
        def onCross(self, vehicle):
            vehicle.damage(10)
    Comme vous pouvez le constater, le fichier "grass_radioactive.block" ne comporte pas d'include vers la classe AbstractBlock, car en effet je veux que ce soit un fichier de script et donc qu'il se limite au minimum et que les import se fassent en interne, pas dans le script. Je pourrais toujours rajouter le "import" dans la chaine de caractère avant de la passer à eval().

    De plus, une fois tout les fichiers chargés, je voudrais avoir un dict contenant pour clé le nom du fichier (sans l'extension) et comme valeur la classe contenue dedans, par exemple ici mon dict peut être représenté comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    blocks = {"radioactive_grass": RadioactiveGrassBlock}
    Je ne sais pas si cette méthode est trop dure à implémenter (à priori non à l'aide de eval()) ni si elle est "sâle" ou pas, mais je tiens absolument à séparer les fichiers de script du "moteur" du jeu.

    Auriez vous déjà des idées pour charger ces classes dans le dict ? Car je ne vois pas comment faire avec eval()...
    Merci d'avance pour vos éventuelles remarques et réponses

  2. #2
    Membre Expert
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Par défaut
    eval évalue une expression, mais ici c'est une bloc d'instructions et c'est donc plutôt exec qu'il faut utiliser (ou, dans ton cas, execfile est sans doute plus approprié).

    Ajouter une instruction import ... n'est pas nécessaire, tu peux passer un dictionnaire en argument (globals) à eval/exec/execfile qui contient déjà le module nécessaire chargé.

    Pour récupérer la classe après exécution, à priori je pense à parcourir le dictionnaire passé en globals au script à la recherche d'une sous-classe de AbstractBlock.

    Si le principe est de permettre aux utilisateurs de scripter ton application, c'est bien sûr très dangereux d'exécuter du code python arbitraire, mais je suppose que tu es conscient de ça...

  3. #3
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut
    Bonsoir.

    Citation Envoyé par dividee Voir le message
    ... c'est bien sûr très dangereux d'exécuter du code python arbitraire, mais je suppose que tu es conscient de ça...
    eval is evil !

  4. #4
    Membre Expert
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Par défaut
    Citation Envoyé par rambc Voir le message
    eval is evil !
    Bert is evil! go back!
    (pour ceux qui comprennent )

  5. #5
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bonjour,

    Comme ceci ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    #!/usr/bin/env python
    # -*- coding: ISO8859-1 -*-
    #
    #
    class AbstractBlock:
        def __init__(self):
            pass
     
        def onCross(self, vehicle):
            pass
     
    grass_radioactive = """class GrassRadioactiveBlock(AbstractBlock):
        def __init__(self):
            pass
     
        def onCross(self, vehicle):
            return vehicle"""
     
    exec(grass_radioactive) # Ou execfile si c'est un fichier
     
    blocks = {"radioactive_grass": GrassRadioactiveBlock}
     
    inst = blocks['radioactive_grass']()
    print(inst.onCross('Patin à roulette'))
    print(inst)
    Mais bon... Utiliser exec/eval pour du code Python...
    Vous devriez suivre le conseil de dividee et penser à un autre système.

    @+

  6. #6
    Membre averti
    Profil pro
    Inscrit en
    Février 2011
    Messages
    51
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2011
    Messages : 51
    Par défaut
    Merci pour vos réponses, j'irai faire un tour sur la doc pour exec et execfile

    Sinon, je suis conscient des risques mais le jeu sera lui-même opensource, alors entre mettre des failles dans le code source ou dans les scripts, je vois pas trop la différence. Sur un plan technique, on peut imaginer un site officiel qui distribue les "extensions" (ensemble de scripts et textures) qui vérifie ces extensions avant de les publier.
    Non, pour moi, cela n'introduit pas trop de grosses failles, je ne vois pas l'intérêt de mettre des failles dans les scripts alors que ce serai plus simple dans le code source

    Sinon, au niveau du script, j'aimerai alléger le plus possible les fichiers de script. Prenons ici le fichier d'extension .block précedent :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class GrassRadioactiveBlock(AbstractBlock):
        def __init__(self):
            pass
     
        def onCross(self, vehicle):
            vehicle.damage(10)
    Il pourrait alors devenir :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    def __init__(self):
    	pass
     
    def onCross(self, vehicle):
    	vehicle.damage(10)
    Le nom de la classe n'ayant pas d'importance (car gestion par dictionnaire), il pourra être généré par le nom du fichier. Je veux vraiment que l'on évite les détails techniques dans les scripts (redéclarer une classe à chaque fois me parait abusif pour le scripteur ).

    Mais bon, je fonce tête baissée dans mon idée mais je ne sais pas du tout si elle sera maintenable dans le temps et pas trop compliquée à mettre en place. J'attends donc vos avis sur ce système en lui même, si on exclue bien sûr les éventuelles failles de sécurité (comme j'en ai parlé plus haut).

    Merci pour vos réponses

    EDIT :

    Vous devriez suivre le conseil de dividee et penser à un autre système.
    Je n'ai pas compris ce conseil
    Mais sinon, qu'avez vous d'autre à proposer ? Garder du code python générique et s'embêter à faire des import en boucle ?

  7. #7
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Citation Envoyé par darkrojo Voir le message
    Sinon, au niveau du script, j'aimerai alléger le plus possible les fichiers de script. Prenons ici le fichier d'extension .block précedent :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class GrassRadioactiveBlock(AbstractBlock):
        def __init__(self):
            pass
     
        def onCross(self, vehicle):
            vehicle.damage(10)
    Il pourrait alors devenir :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    def __init__(self):
    	pass
     
    def onCross(self, vehicle):
    	vehicle.damage(10)
    Le nom de la classe n'ayant pas d'importance (car gestion par dictionnaire), il pourra être généré par le nom du fichier. Je veux vraiment que l'on évite les détails techniques dans les scripts (redéclarer une classe à chaque fois me parait abusif pour le scripteur )
    Dans ce cas autant éviter Python et passer directement par un fichier de configuration avec section. Comprendre (par exemple)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    [onCross]
    vehicle.damage = 10
    A vous de traiter le fichier avec Python dans votre code

    Sinon, comme dirait dividee, You've been warned

  8. #8
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut
    Citation Envoyé par dividee Voir le message
    Bert is evil! go back!
    (pour ceux qui comprennent )
    Moi pas comprendre...

  9. #9
    Membre Expert

    Homme Profil pro
    Diverses et multiples
    Inscrit en
    Mai 2008
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Diverses et multiples

    Informations forums :
    Inscription : Mai 2008
    Messages : 662
    Par défaut
    Citation Envoyé par rambc Voir le message
    Moi pas comprendre...
    Bah, moi non plus…

  10. #10
    Membre Expert
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Par défaut
    Je propose ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class AbstractBlock:
        def __init__(self):
            pass
     
        def onCross(self, vehicle):
            pass
     
    def load_class(name, bases, filename):
        attrs = {'__builtins__' : __builtins__ }
        execfile(filename, attrs)
        del attrs['__builtins__']
        globals()[name] = type(name, bases, attrs)
     
     
    load_class("GrassRadioactiveBlock",(AbstractBlock,),"grass_radioactive.block")
    Citation Envoyé par dividee Voir le message
    Bert is evil! go back!
    (pour ceux qui comprennent )
    Bah, c'était un référence à Python Challenge. Il semblerait que pas grand monde ici soit arrivé jusqu'au niveau 13

  11. #11
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bonjour dividee,

    [courge_attitude]

    Pourquoi __builtins__ ?

    Dans le cadre d'une parenté comme décrite plus haut, soit class GrassRadioactiveBlock(AbstractBlock), je ne vois pas comment AbstractBlock peut être visible pour GrassRadioactiveBlock au sein d'exec s'il n'est pas dans attrs.

    type(name, bases, attrs) avec (AbstractBlock,) ? Dans ce cas cela ne fonctionne pas si nous n'avons pas class AbstractBlock(object), non ?

    Il faudrait faire une fonction execfile pour Python 3 pour être compatible.

    Pourquoi le del ?

    [/courge_attitude]

    @+

  12. #12
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bon... C'était bien ma courge attitude du week-end.
    J'étais partis sur un grass_radioactive.block avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class GrassRadioactiveBlock(AbstractBlock):
    ...
    et non
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    def __init__(self):
        pass
    comme demander.

    Par contre mon questionnement reste pour ce qui est de l'utilisation de __builtins__ et type.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class AbstractBlock(object):
        def __init__(self):
            pass
     
        def onCross(self, vehicle):
            pass
     
    def loadfile(name):
        with open(name) as f:
            return f.read()
     
    def load_class(name, bases, filename):
        attrs = {}
        exec(loadfile(filename), attrs)
        globals()[name] = type(name, bases, attrs)
     
    load_class("GrassRadioactiveBlock", (AbstractBlock,), "grass_radioactive.block")
    fonctionne tout aussi bien.

    @+

  13. #13
    Membre Expert
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Par défaut
    Bonjour PauseKawa,

    pour l'héritage de object, tu as raison je l'avais oublié...

    Pour l'ajout de __builtins__ dans le dico avant l'appel de exec/execfile, c'est parce que je pensais que l'argument globals était traité comme pour eval:
    If the globals dictionary is present and lacks ‘__builtins__’, the current globals are copied into globals before expression is parsed.
    Je voulais éviter que les variables globales actuelles soient passées au script et ne viennent polluer attrs. Mais effectivement, en testant un peu, il semble que seul __builtins__ soit copié dans attrs s'il est vide; on peut donc passer un dictionnaire vide.

    Par contre, le del de __builtins__ reste nécessaire, sinon tu te retrouves avec une classe qui contient un attribut __builtins__

  14. #14
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Merci pour la réponse, je commençais à avoir une surchauffe de mon monoméninge à force de chercher le pourquoi

  15. #15
    Membre Expert

    Homme Profil pro
    Diverses et multiples
    Inscrit en
    Mai 2008
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Diverses et multiples

    Informations forums :
    Inscription : Mai 2008
    Messages : 662
    Par défaut
    À propos de l’héritage de object, on n’est plus obligé de le préciser explicitement, si*?

    normalement, les parenthèses suffisent, object est alors implicitement le parent*:


  16. #16
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Pas vraiment

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    >>> class Foo():
    ...     pass
    ... 
    >>> class Foo1(object):
    ...     pass
    ... 
    >>> print(dir(Foo))
    ['__doc__', '__module__']
    >>> print(dir(Foo1))
    ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
    >>> print(type(Foo))
    <type 'classobj'>
    >>> print(type(Foo1))
    <type 'type'>

  17. #17
    Membre éclairé
    Profil pro
    Inscrit en
    Juin 2011
    Messages
    43
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2011
    Messages : 43
    Par défaut
    Avec Python 3, il n'y a plus de différence entre new-style et les classic-style. Aussi, les parenthèses ne sont pas indispensable pour les classes n'héritant que de object. Toutefois, pour rendre un code portable d'une version 3 à 2 de python, il est évidemment indispensable d'utiliser la notation dite New-Style (ç-à-d: en précisant l'héritage de object).

  18. #18
    Membre Expert

    Homme Profil pro
    Diverses et multiples
    Inscrit en
    Mai 2008
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Diverses et multiples

    Informations forums :
    Inscription : Mai 2008
    Messages : 662
    Par défaut
    Ah oui, effectivement, j’étais en train de faire un backport optimiste

  19. #19
    Membre Expert
    Profil pro
    Développeur en systèmes embarqués retraité
    Inscrit en
    Mars 2006
    Messages
    952
    Détails du profil
    Informations personnelles :
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Mars 2006
    Messages : 952
    Par défaut
    Salut,

    Citation Envoyé par dividee Voir le message
    Bah, c'était un référence à Python Challenge. Il semblerait que pas grand monde ici soit arrivé jusqu'au niveau 13
    Ben bravo, je sens que ça va bien me pourrir la semaine, là... Même pas pu résoudre la numéro 1! Mais comme je suis têtu je sens que je vais y passer du temps.

    A+

    Pfeuh

  20. #20
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Citation Envoyé par pfeuh Voir le message
    Salut,



    Ben bravo, je sens que ça va bien me pourrir la semaine, là... Même pas pu résoudre la numéro 1! Mais comme je suis têtu je sens que je vais y passer du temps.

    A+

    Pfeuh
    2^

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. [Système] Exécution d'un script SHELL avec PHP
    Par diabli73 dans le forum Langage
    Réponses: 5
    Dernier message: 02/10/2008, 15h07
  2. [Système] Lancer un script perl avec PHP
    Par pepite dans le forum Langage
    Réponses: 2
    Dernier message: 09/01/2006, 12h30
  3. Réponses: 6
    Dernier message: 23/05/2005, 08h33
  4. Script SQL avec des EXIT SQL.SQLCODE
    Par fidififouille dans le forum Oracle
    Réponses: 14
    Dernier message: 23/04/2004, 16h45
  5. Génération de script SQL avec les données
    Par borgfabr dans le forum MS SQL Server
    Réponses: 3
    Dernier message: 05/03/2004, 13h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo