web-dev-qa-db-fra.com

Le même nom fonctionne dans la même classe, une manière élégante de déterminer lequel appeler?

J'essaie de contrôler les versions du produit dans des scripts Python pour une raison spécifique, mais je ne pouvais pas comprendre comment le faire de manière élégante - veuillez aider.

Actuellement, je fais quelque chose comme ci-dessous. Cependant, les scripts sont difficiles à gérer lorsque le contenu de la version est modifié.

class Product(object):

    def __init__(client):
        self.version = client.version  # get client version from another module

    def function():
        if self.version == '1.0':
            print('for version 1.0')
        Elif self.version == '2.0':
            print('for version 2.0')
        else:
            print(f'function not support {self.version}')

Par conséquent, je veux faire quelque chose comme ce qui suit pour séparer les fonctions du même nom.

class Product(object):

    def __init__(client):
        self.version = client.version  # get client version from another module

    def function():
        print('for version 1.0')

    def function():
        print('for version 2.0')

Je pensais utiliser décorateur pour y parvenir:

class Product(object):

    def __init__(client):
        self.version = client.version  # get client version from another module

    @version(1.0)
    def function():
        print('for version 1.0')

    @version(2.0)
    def function():
        print('for version 2.0')

Cependant, je n'ai pas compris comment ... on dirait qu'un décorateur ne peut pas faire cette opération ou que je ne comprends tout simplement pas comment.

Y a-t-il une manière élégante de faire ceci?

35
Timmy Lin

L'héritage est probablement le meilleur moyen de le faire, mais puisque vous avez posé une question spécifique sur les décorateurs, je voulais montrer que vous pouvez le faire en utilisant des décorateurs.

Vous devrez utiliser un dictionnaire pour stocker vos fonctions par version, puis rechercher la version à utiliser au moment de l'exécution. Voici un exemple.

version_store = {}

def version(v):
    def dec(f):
        name = f.__qualname__
        version_store[(name, v)] = f
        def method(self, *args, **kwargs):
            f = version_store[(name, self.version)]
            return f(self, *args, **kwargs)
        return method
    return dec

class Product(object):
    def __init__(self, version):
        self.version = version

    @version("1.0")
    def function(self):
        print("1.0")

    @version("2.0")
    def function(self):
        print("2.0")

Product("1.0").function()
Product("2.0").function()
33
Bi Rico

Pourriez-vous mettre votre classe Product dans deux modules, v1 et v2, puis les importer de manière conditionnelle?

Par exemple:

Productv1.py

class Product(object):
    def function():
        print('for version 1.0')

Productv2.py

class Product(object):
    def function():
        print('for version 2.0')

Puis dans votre fichier principal:

main.py

if client.version == '1.0':
    from Productv1 import Product
Elif client.version == '2.0':
    from Productv2 import Product
else:
    print(f'function not support {self.version}')

p = Product
p.function()
9
Loocid

Une autre option consiste à créer une classe dans une usine.

Créez vos fonctions versionnées (notez le paramètre self). Cela peut être fait dans un module différent. Ajoutez également une collection pour récupérer la fonction en fonction du numéro de version.

def func_10(self):
    print('for version 1.0')

def func_20(self):
    print('for version 2.0')

funcs = {"1.0": func_10,
         "2.0": func_20}

Ajoutez une classe de base contenant les parties statiques de votre implémentation et une classe utilitaire pour créer vos instances dans:

class Product:
    def __init__(self, version):
        self.version = version

class ProductFactory(type):
    @classmethod
    def get_product_class(mcs, version):
        # this will return an instance right away, due to the (version) in the end
        return type.__new__(mcs, "Product_{}".format(version.replace(".","")), (Product,), {"function": funcs.get(version)})(version)
        # if you want to return a class object to instantiate in your code omit the (version) in the end

En utilisant ceci:

p1 = ProductFactory.get_product_class("1.0")
p2 = ProductFactory.get_product_class("2.0")
print(p1.__class__.__name__) # Product_10
p1.function() # for version 1.0
print(p1.function) # <bound method func_10 of <__main__.Product_10 object at 0x0000000002A157F0>>
print(p2.__class__.__name__) # Product_20
p2.function() # for version 2.0 
print(p2.function) # <bound method func_20 of <__main__.Product_20 object at 0x0000000002A15860>>
7
shmee

En général, ne pas. surcharge de méthode est déconseillé en Python. Si vous devez vous différencier au niveau de la classe, lisez la réponse de @ Loocid. Je ne vais pas répéter son excellent post.

Si vous voulez entrer dans un niveau de méthode parce que la différence est assez petite pour cela, essayez quelque chose comme ceci:

class Product:

    def method(self):
        if self.version == "1.0":
            return self._methodv1()
        Elif self.version == "2.0":
            return self._methodv2()
        else:
            raise ValueError("No appropriate method for version {}".format(self.version))

    def _methodv1(self):
        # implementation

    def _methodv2(self):
        # implementation

Notez ici:

  1. Utilisation de méthodes commençant par un trait de soulignement pour indiquer qu'il s'agit de l'implémentation.
  2. Garder les méthodes agréables et bien rangées sans contamination entre les versions
  3. Relever une erreur pour des versions inattendues (crash précoce et difficile).
  4. À mon avis pas si humble, ce sera beaucoup plus clair pour les autres lecteurs de votre post que pour les décorateurs.

Ou:

class Product:

    def method_old(self):
        # transform arguments to v2 method:
        return self.method()

    def method(self):
        # implementation
  1. Si vous souhaitez totalement déconseiller l’utilisation précédente et supprimer la prise en charge de la version 1.0 à l’avenir.
  2. Assurez-vous de donner des avertissements de dépréciation afin de ne pas surprendre les utilisateurs d'une bibliothèque par des changements soudains.
  3. On peut dire que c'est la meilleure solution si personne d'autre n'utilise votre code.

La première méthode me convient mieux, mais je voulais inclure la seconde pour la postérité. Si vous modifiez votre code dans 10 ans, celui-ci vous rendra plus heureux. Si vous modifiez le code en utilisant le code actuel demain, la première méthode vous rendra plus heureux.

6
Jacco van Dorp

Ou tu peux faire, dict.get appeler une fonction et faire print dans lambda si rien n’est correct:

def func_1(self):
        print('for version 1.0')

    def func_2(self):
        print('for version 2.0')
    def function(self):
       funcs = {"1.0": self.func_1,
                "2.0": self.func_2}
       funcs.get(self.version,lambda: print(f'function not support {self.version}'))()

Exemple:

class Product(object):

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

    def func_1(self):
        print('for version 1.0')

    def func_2(self):
        print('for version 2.0')
    def function(self):
       funcs = {"1.0": self.func_1,
                "2.0": self.func_2}
       funcs.get(self.version,lambda: print(f'function not support {self.version}'))()
Product('1.0').function()
Product('2.0').function()
Product('3.0').function()

Sortie:

for version 1.0
for version 2.0
function not support 3.0
2
U10-Forward

Je ne suis pas un développeur python mais je ne peux pas m'empêcher de me demander pourquoi vous ne faites pas quelque chose comme ça:

class Product(object):
    def __init__(self, version):
        self.version = version
    def function(self):
        print('for version ' + self.version)
2
Jack

Vous pouvez utiliser des décorateurs pour cela

def v_decorate(func):
   def func_wrapper(name):
       return func(name)
   return func_wrapper

Et

@v_decorate
def get_version(name):
   return "for version {0} ".format(name)

Vous pouvez l'appeler

get_version(1.0)

   'for version 1.0 '

get_version(2.0)
'for version 2.0 '
1
Richard Rublev