Je construis des classes héritées de float pour respecter une dimension dans certains calculs chimiques. par exemple.:

class volume(float):
  def __init__(self, value):
    float._init_(value)

Maintenant, mon objectif est:

  1. pour lever une erreur chaque fois que + ou - est utilisé entre un float normal et une instance de volume
  2. renvoie une instance de volume, chaque fois que + ou - est utilisé entre deux instances de volume
  3. renvoie une instance de volume, chaque fois que * est utilisé (des deux côtés) et / est utilisé (de gauche)
  4. déclenche une erreur lorsque / est utilisé depuis la gauche
  5. renvoie un float chaque fois que deux instances de volume sont divisées.

Pour le moment, je vais remplacer tous ces quatre opérateurs, de gauche à droite (par exemple _ add _ et _ radd _);

err='Only objects of type volume can be added or subtracted together'
def __add__(self,volume2):
  if isinstance(volume2,volume): return volume(float(self)+volume2)
  else: raise TypeError(err)
def __radd__(self,volume2):
  if isinstance(volume2,volume): return volume(float(self)+volume2)
  else: raise TypeError(err)

Existe-t-il un moyen plus simple d'accéder à tous, ou au moins une expression pour inclure à la fois les utilisations gauche et droite de l'opérateur?

1
Zara 30 août 2020 à 21:52

2 réponses

Meilleure réponse

Il semble que cette question vise principalement à éviter la duplication de code. En ce qui concerne les cas de multiplication et de division, vous avez des fonctionnalités légèrement différentes et vous devrez peut-être écrire des méthodes distinctes explicitement, mais pour les méthodes liées à l'addition et à la soustraction, la technique suivante fonctionnerait. Il s'agit essentiellement d'un patching de singe pour la classe, et il est normal de le faire, bien que vous ne devriez pas essayer de la même manière que les instances de monkey-patch dans Python 3.

J'ai appelé la classe Volume avec V majuscule conformément à la convention.

class Volume(float):
  pass

def _make_wrapper(name):
    def wrapper(self, other):
        if not isinstance(other, Volume):
            raise ValueError
        return Volume(getattr(float, name)(self, other))
    setattr(Volume, name, wrapper)

for _method in ('__add__', '__radd__', 
                '__sub__', '__rsub__',):                    
    _make_wrapper(_method)

La méthode explicite équivalente dans ces cas ressemble à ce qui suit, alors adaptez-vous comme requis pour les cas de multiplication / division, mais notez l'utilisation explicite de float.__add__(self, other) plutôt que de self + other comme la question suggère que vous aviez l'intention (où la question mentionne volume(self+volume2)), ce qui conduirait à une récursion infinie.

    def __add__(self, other):
        if not isinstance(other, Volume):
            raise ValueError
        return Volume(float.__add__(self, other))        

Concernant le __init__, je l'ai maintenant supprimé ci-dessus, car s'il ne fait qu'appeler float.__init__, alors il n'est pas du tout nécessaire de le définir (laissez-le simplement hériter __init__ du classe de base). Si vous voulez avoir une méthode __init__ pour initialiser quelque chose d'autre , alors oui, vous devrez également inclure l'appel explicite à float.__init__ comme vous le faites dans la question (bien que notez les doubles traits de soulignement - dans la question que vous essayez d'appeler float._init_).

2
alani 30 août 2020 à 19:21

metaclass est un moyen de contrôler la construction de class.

Vous pouvez utiliser metaclasses pour surcharger tous les opérateurs mathématiques comme ceci:

err='Only objects of type volume can be added or subtracted together'

class OverLoadMeta(type):
    def __new__(meta,name,bases,dct):
        # this is the operation you want to use instead of default add or subtract.
        def op(self,volume2):
            if isinstance(volume2,Volume): 
                return Volume(float.__add__(self,volume2))
            else:
                raise TypeError(err)
        # you can overload whatever method you want here
        for method in ('__add__','__radd__','__sub__'):
            dct[method] = op
        
        return super(OverLoadMeta, meta).__new__(meta, name, bases, dct)


class Volume(float,metaclass=OverLoadMeta):
    ""


# you can use it like this:
a = Volume(1)
b = Volume(2)

c = a+b
print(c.__class__)
# class will be <class '__main__.Volume'>

a + 1
# raise TypeError: Only objects of type volume can be added or subtracted together

0
fusion 30 août 2020 à 19:33