with - python property decorator




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

J'essaie de contrôler les versions de produit dans les scripts Python pour une raison spécifique, mais je ne savais pas 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 un 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?


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)

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()

Pourriez-vous mettre votre classe de 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()

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>>

En général, non. La surcharge de méthodes est déconseillée 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 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 deuxième 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.





python-decorators