web-dev-qa-db-fra.com

Utiliser @property contre les getters et les setters

Voici une question de conception spécifique à Python:

class MyClass(object):
    ...
    def get_my_attr(self):
        ...

    def set_my_attr(self, value):
        ...

et

class MyClass(object):
    ...        
    @property
    def my_attr(self):
        ...

    @my_attr.setter
    def my_attr(self, value):
        ...

Python nous permet de le faire de toute façon. Si vous conceviez un programme Python, quelle approche utiliseriez-vous et pourquoi?

701
Zaur Nasibov

Préférer les propriétés. C'est pour ça qu'ils sont là.

La raison en est que tous les attributs sont publics en Python. Les noms de départ marqués par un ou deux caractères soulignent simplement que l'attribut donné est un détail d'implémentation qui risque de ne pas rester le même dans les futures versions du code. Cela ne vous empêche pas d'obtenir ou de définir cet attribut. Par conséquent, l’accès aux attributs standard est la méthode normale d’accès aux attributs en Pythonic.

L’avantage des propriétés est qu’elles sont syntaxiquement identiques à l’accès aux attributs. Vous pouvez donc passer d’un élément à l’autre sans modifier le code client. Vous pourriez même avoir une version d'une classe qui utilise des propriétés (par exemple, pour le code par contrat ou le débogage) et une version qui n'en utilise pas pour la production, sans changer le code qui l'utilise. En même temps, vous n'avez pas besoin d'écrire des getters et des setters pour tout, au cas où vous auriez besoin de mieux contrôler l'accès plus tard.

589
kindall

Dans Python, vous n'utilisez pas de getters, de setters ou de propriétés uniquement pour le plaisir. Vous commencez par utiliser des attributs, puis plus tard, si nécessaire, par la suite, vous migrez vers une propriété sans avoir à modifier le code à l'aide de vos classes.

Il existe en effet beaucoup de code d’extension .py qui utilise des accesseurs, des héritiers et des classes inutiles partout, par exemple. un simple tuple conviendrait, mais c'est du code de personnes écrivant en C++ ou Java à l'aide de Python.

Ce n'est pas du code Python.

148
6502

L'utilisation de propriétés vous permet de commencer par des accès d'attributs normaux, puis sauvegardez-les ensuite avec des accesseurs et des setters si nécessaire .

115

La réponse courte est: la propriété gagne haut la main . Toujours.

Il y a parfois un besoin d’obstacles et de passeurs, mais même dans ce cas, je les "cacherais" au monde extérieur. Il y a de nombreuses façons de le faire dans Python (getattr, setattr, __getattribute__, etc ..., mais un très concis et propre est:

def set_email(self, value):
    if '@' not in value:
        raise Exception("This doesn't look like an email address.")
    self._email = value

def get_email(self):
    return self._email

email = property(get_email, set_email)

Voici un bref article qui introduit le sujet des getters et des setters en Python.

69
mac

[TL; DR? Vous pouvez passer à la fin pour un exemple de code.]

En fait, je préfère utiliser un autre langage, ce qui est un peu compliqué à utiliser, mais Nice si vous avez un cas d'utilisation plus complexe.

Un peu de fond d'abord.

Les propriétés sont utiles dans la mesure où elles nous permettent de gérer les paramètres et d'obtenir les valeurs de manière programmatique, tout en permettant l'accès aux attributs en tant qu'attributs. Nous pouvons transformer les "entrées" en "calculs" (essentiellement) et nous pouvons transformer les "ensembles" en "événements". Supposons donc que nous ayons la classe suivante, que j'ai codée avec des getters et des setters de type Java.

class Example(object):
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y

    def getX(self):
        return self.x or self.defaultX()

    def getY(self):
        return self.y or self.defaultY()

    def setX(self, x):
        self.x = x

    def setY(self, y):
        self.y = y

    def defaultX(self):
        return someDefaultComputationForX()

    def defaultY(self):
        return someDefaultComputationForY()

Vous vous demandez peut-être pourquoi je n'ai pas appelé defaultX et defaultY dans la méthode __init__ de l'objet. La raison en est que pour notre cas, je veux supposer que les méthodes someDefaultComputation renvoient des valeurs qui varient dans le temps, par exemple, un horodatage, et chaque fois que x (ou y) n'est pas défini (où Pour les besoins de cet exemple, "non défini" signifie "défini sur Aucun". Je veux la valeur du calcul par défaut de x (ou de y]).

Donc, c'est boiteux pour un certain nombre de raisons décrites ci-dessus. Je vais le réécrire en utilisant les propriétés:

class Example(object):
    def __init__(self, x=None, y=None):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self.x or self.defaultX()

    @x.setter
    def x(self, value):
        self._x = value

    @property
    def y(self):
        return self.y or self.defaultY()

    @y.setter
    def y(self, value):
        self._y = value

    # default{XY} as before.

Qu'avons-nous gagné? Nous avons acquis la possibilité de faire référence à ces attributs en tant qu'attributs même si, en coulisse, nous finissons par exécuter des méthodes.

Bien sûr, le vrai pouvoir des propriétés est que nous souhaitons généralement que ces méthodes agissent en plus d’obtenir et de définir des valeurs (sinon, il n’est pas utile d’utiliser des propriétés). Je l'ai fait dans mon exemple de getter. Nous exécutons essentiellement un corps de fonction pour récupérer une valeur par défaut chaque fois que la valeur n'est pas définie. C'est un modèle très commun.

Mais que perdons-nous et que ne pouvons-nous pas faire?

Le principal inconvénient, à mon avis, est que si vous définissez un getter (comme nous le faisons ici), vous devez également définir un setter. [1] C'est le bruit supplémentaire qui encombre le code.

Un autre inconvénient est que nous devons toujours initialiser les valeurs x et y dans __init__. (Bien sûr, nous pourrions les ajouter en utilisant setattr() mais c'est du code supplémentaire.)

Troisièmement, contrairement à l'exemple de type Java, les getters ne peuvent pas accepter d'autres paramètres. Maintenant, je vous entends déjà dire, eh bien, si on prend des paramètres, ce n'est pas un getter! Dans un sens officiel, c'est vrai. Mais dans la pratique, il n’ya aucune raison pour que nous ne soyons pas en mesure de paramétrer un attribut nommé - comme x - et de définir sa valeur pour certains paramètres spécifiques.

Ce serait bien si nous pouvions faire quelque chose comme:

e.x[a,b,c] = 10
e.x[d,e,f] = 20

par exemple. Le plus proche que nous puissions obtenir est de remplacer l'assignation pour impliquer une sémantique spéciale:

e.x = [a,b,c,10]
e.x = [d,e,f,30]

et bien sûr, assurez-vous que notre auteur sait comment extraire les trois premières valeurs en tant que clé d'un dictionnaire et définir sa valeur sur un nombre ou quelque chose d'autre.

Mais même si nous le faisions, nous ne pourrions toujours pas le supporter avec des propriétés car il n’existait aucun moyen d’obtenir la valeur car nous ne pouvions pas du tout transmettre de paramètres au getter. Nous avons donc dû tout renvoyer, en introduisant une asymétrie.

Le getter/setter de style Java nous permet de gérer cela, mais nous avons de nouveau besoin de getter/setters.

Dans mon esprit, ce que nous voulons vraiment, c’est quelque chose qui capture les exigences suivantes:

  • Les utilisateurs définissent une seule méthode pour un attribut donné et peuvent indiquer si l'attribut est en lecture seule ou en lecture-écriture. Les propriétés échouent à ce test si l'attribut est accessible en écriture.

  • Il n'est pas nécessaire que l'utilisateur définisse une variable supplémentaire sous-jacente à la fonction. Par conséquent, nous n'avons pas besoin de __init__ ou setattr dans le code. La variable existe simplement du fait que nous avons créé cet attribut new-style.

  • Tout code par défaut pour l'attribut est exécuté dans le corps même de la méthode.

  • Nous pouvons définir l'attribut en tant qu'attribut et le référencer en tant qu'attribut.

  • Nous pouvons paramétrer l'attribut.

En termes de code, nous voulons un moyen d'écrire:

def x(self, *args):
    return defaultX()

et pouvoir ensuite faire:

print e.x     -> The default at time T0
e.x = 1
print e.x     -> 1
e.x = None
print e.x     -> The default at time T1

et ainsi de suite.

Nous souhaitons également un moyen de procéder de la sorte dans le cas particulier d’un attribut paramétrable, tout en permettant au cas d’affectation par défaut de fonctionner. Vous verrez comment j'ai abordé cette question ci-dessous.

Maintenant au point (yay! Le point!). La solution pour laquelle j'ai proposé cette solution est la suivante.

Nous créons un nouvel objet pour remplacer la notion de propriété. L'objet est destiné à stocker la valeur d'une variable définie, mais conserve également un handle sur le code qui sait comment calculer une valeur par défaut. Son travail consiste à stocker l'ensemble value ou à exécuter le method si cette valeur n'est pas définie.

Appelons cela un UberProperty.

class UberProperty(object):

    def __init__(self, method):
        self.method = method
        self.value = None
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def clearValue(self):
        self.value = None
        self.isSet = False

Je suppose que method voici une méthode de classe, value est la valeur de la UberProperty, et j’ai ajouté isSet car None peut être une valeur réelle et Cela nous permet de déclarer clairement qu’il n’ya vraiment aucune valeur. Une autre manière est une sentinelle.

Cela nous donne fondamentalement un objet qui peut faire ce que nous voulons, mais comment pouvons-nous le mettre réellement sur notre classe? Eh bien, les propriétés utilisent des décorateurs; pourquoi pas nous Voyons à quoi cela pourrait ressembler (à partir de maintenant, je vais m'en tenir à un simple 'attribut', x).

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

Cela ne fonctionne pas encore, bien sûr. Nous devons implémenter uberProperty et nous assurer qu'il gère à la fois les acquisitions et les ensembles.

Commençons par obtient.

Ma première tentative a été de créer simplement un nouvel objet UberProperty et de le renvoyer:

def uberProperty(f):
    return UberProperty(f)

Bien sûr, j'ai vite découvert que cela ne fonctionnait pas: Python ne lie jamais l'appelable à l'objet et j'ai besoin de l'objet pour appeler la fonction. Même créer le décorateur dans la classe ne fonctionne pas, car bien que maintenant nous avons la classe, nous n'avons toujours pas d'objet avec lequel travailler.

Nous allons donc avoir besoin de pouvoir faire plus ici. Nous savons qu'une méthode n'a besoin d'être représentée qu'une seule fois, alors allons-y et gardons notre décorateur, mais modifions UberProperty pour ne stocker que la référence method:

class UberProperty(object):

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

Ce n'est pas non plus appelable, donc pour le moment rien ne fonctionne.

Comment complétons-nous le tableau? Eh bien, que finissons-nous lorsque nous créons l'exemple de classe à l'aide de notre nouveau décorateur:

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

print Example.x     <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x   <__main__.UberProperty object at 0x10e1fb8d0>

dans les deux cas, nous récupérons la UberProperty qui, bien entendu, n'est pas appelable, ce qui ne sert donc à rien.

Nous avons besoin d'un moyen de lier dynamiquement l'instance UberProperty créée par le décorateur après la création de la classe à un objet de la classe avant que cet objet ne soit renvoyé à cet utilisateur pour utilisation. Euh, ouais, c'est un appel __init__, mec.

Écrivons ce que nous voulons que notre résultat de recherche soit le premier. Nous lions un UberProperty à une instance, une chose évidente à renvoyer serait donc BoundUberProperty. C’est là que nous allons réellement maintenir l’état pour l’attribut x.

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

Maintenant nous la représentation; comment les obtenir sur un objet? Il existe quelques approches, mais la plus facile à expliquer utilise simplement la méthode __init__ pour effectuer ce mappage. Au moment où __init__ s'appelle nos décorateurs ont exécuté, il suffit donc de regarder à travers le __dict__ de l'objet et de mettre à jour tous les attributs pour lesquels la valeur de l'attribut est de type UberProperty.

Maintenant, uber-properties est cool et nous voudrons probablement les utiliser beaucoup, il est donc logique de créer une classe de base qui le fait pour toutes les sous-classes. Je pense que vous savez comment on appellera la classe de base.

class UberObject(object):
    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)

Nous ajoutons ceci, changeons notre exemple pour hériter de UberObject, et ...

e = Example()
print e.x               -> <__main__.BoundUberProperty object at 0x104604c90>

Après avoir modifié x pour être:

@uberProperty
def x(self):
    return *datetime.datetime.now()*

Nous pouvons exécuter un test simple:

print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()

Et nous obtenons le résultat souhaité:

2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310

(Gee, je travaille tard.)

Notez que j'ai utilisé getValue, setValue et clearValue ici. C'est parce que je n'ai pas encore de lien dans les moyens de les renvoyer automatiquement.

Mais je pense que c’est un bon endroit pour arrêter pour le moment, car je commence à être fatiguée. Vous pouvez également constater que la fonctionnalité essentielle que nous recherchions est en place. le reste est habillé de fenêtre. Important habillage de la fenêtre d’utilisation, mais cela peut attendre jusqu’à ce que j’ai un changement pour mettre à jour le message.

Je terminerai l’exemple dans le prochain message en abordant les points suivants:

  • Nous devons nous assurer que le __init__ d'UberObject est toujours appelé par les sous-classes.

    • Nous obligeons donc à l'appeler quelque part ou l'empêchons d'être mis en œuvre.
    • Nous verrons comment faire cela avec une métaclasse.
  • Nous devons nous assurer de gérer le cas courant dans lequel une personne "alias" remplit une fonction, par exemple:

      class Example(object):
          @uberProperty
          def x(self):
              ...
    
          y = x
    
  • Nous avons besoin de e.x pour renvoyer e.x.getValue() par défaut.

    • Nous verrons que c’est un domaine où le modèle échoue.
    • Il s'avère qu'il faudra toujours utiliser un appel de fonction pour obtenir la valeur.
    • Mais nous pouvons le faire ressembler à un appel de fonction normal et éviter de devoir utiliser e.x.getValue(). (Faire celui-ci est évident, si vous ne l'avez pas déjà corrigé.)
  • Nous devons prendre en charge le réglage de e.x directly, comme dans e.x = <newvalue>. Nous pouvons également faire cela dans la classe parente, mais nous devons mettre à jour notre code __init__ pour le gérer.

  • Enfin, nous ajouterons des attributs paramétrés. Il devrait être assez évident de savoir comment nous allons faire cela aussi.

Voici le code tel qu'il existe jusqu'à présent:

import datetime

class UberObject(object):
    def uberSetter(self, value):
        print 'setting'

    def uberGetter(self):
        return self

    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)


class UberProperty(object):
    def __init__(self, method):
        self.method = method

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

    def uberProperty(f):
        return UberProperty(f)

class Example(UberObject):

    @uberProperty
    def x(self):
        return datetime.datetime.now()

[1] Je peux être en retard sur si c'est toujours le cas.

60
Adam Donahue

Je pense que les deux ont leur place. Un problème lié à l'utilisation de @property est qu'il est difficile d'étendre le comportement des getters ou des setters dans les sous-classes à l'aide de mécanismes de classe standard. Le problème est que les fonctions de lecture/définition sont masquées dans la propriété.

Vous pouvez réellement obtenir les fonctions, par exemple avec

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

vous pouvez accéder aux fonctions getter et setter en tant que C.p.fget et C.p.fset, mais vous ne pouvez pas utiliser facilement les fonctionnalités normales d'héritage de méthode (par exemple, super) pour les étendre. Après avoir fouillé dans les subtilités de super, vous pouvez utilisez vraiment super de cette façon:

# Using super():
class D(C):
    # Cannot use super(D,D) here to define the property
    # since D is not yet defined in this scope.
    @property
    def p(self):
        return super(D,D).p.fget(self)

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for D'
        super(D,D).p.fset(self, val)

# Using a direct reference to C
class E(C):
    p = C.p

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for E'
        C.p.fset(self, val)

L'utilisation de super () est toutefois assez fastidieuse, car la propriété doit être redéfinie et vous devez utiliser le mécanisme super légèrement contre-intuitif (cls, cls) pour obtenir une copie non liée de p.

24
NeilenMarais

L'utilisation de propriétés est pour moi plus intuitive et s'intègre mieux dans la plupart des codes.

Comparant

o.x = 5
ox = o.x

vs.

o.setX(5)
ox = o.getX()

est pour moi tout à fait évident qui est plus facile à lire. De plus, les propriétés permettent de créer des variables privées beaucoup plus facilement.

20
Hobblin

Je préférerais ne pas utiliser ni dans la plupart des cas. Le problème avec les propriétés est qu'elles rendent la classe moins transparente. Surtout, c'est un problème si vous leviez une exception auprès d'un passeur. Par exemple, si vous avez une propriété Account.email:

class Account(object):
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError('Invalid email address.')
        self._email = value

alors l'utilisateur de la classe ne s'attend pas à ce que l'attribution d'une valeur à la propriété puisse provoquer une exception:

a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.

En conséquence, l’exception risque de ne pas être gérée et de se propager trop haut dans la chaîne d’appel pour être gérée correctement, ou de donner lieu à un traçage très inutile présenté à l’utilisateur du programme (ce qui est malheureusement trop commun dans le monde de python et Java).

J'éviterais aussi d'utiliser des getters et des setters:

  • parce que les définir à l'avance pour toutes les propriétés prend beaucoup de temps,
  • allonge inutilement la quantité de code, ce qui rend plus difficile la compréhension et la maintenance du code,
  • si vous ne définissiez les propriétés que si cela était nécessaire, l'interface de la classe changerait, ce qui serait préjudiciable à tous les utilisateurs de la classe.

Au lieu des propriétés et des getters/setters, je préfère effectuer la logique complexe dans des endroits bien définis, comme dans une méthode de validation:

class Account(object):
    ...
    def validate(self):
        if '@' not in self.email:
            raise ValueError('Invalid email address.')

ou une méthode similaire Account.save.

Notez que je n'essaie pas de dire qu'il n'y a pas de cas où les propriétés sont utiles, mais seulement qu'il peut être préférable de rendre vos classes simples et transparentes au point de ne pas en avoir besoin.

12
user2239734

Je pense que les propriétés ont pour objectif de vous permettre d’obtenir le temps d’écriture nécessaire pour écrire des accesseurs et des passeurs uniquement lorsque vous en avez réellement besoin.

La culture de programmation Java recommande fortement de ne jamais donner accès aux propriétés, mais plutôt de passer par des accesseurs et des installateurs, et uniquement ceux qui sont réellement nécessaires. Il est un peu verbeux de toujours écrire ces éléments de code évidents et de noter qu'ils ne sont jamais remplacés par une logique non triviale dans 70% des cas.

En Python, les gens s’occupent de ce genre de frais généraux, de sorte que vous puissiez adopter la pratique suivante:

  • Ne pas utiliser les getters et les setters au début, quand ils ne sont pas nécessaires
  • Utilisez @property pour les implémenter sans changer la syntaxe du reste de votre code.
11
fulmicoton

Dans les projets complexes, je préfère utiliser des propriétés en lecture seule (ou des getters) avec une fonction de définition explicite:

class MyClass(object):
...        
@property
def my_attr(self):
    ...

def set_my_attr(self, value):
    ...

Dans les projets de longue durée, le débogage et la refactorisation prennent plus de temps que l'écriture du code lui-même. Il y a plusieurs inconvénients à utiliser @property.setter qui rendent le débogage encore plus difficile:

1) python permet de créer de nouveaux attributs pour un objet existant. Cela rend très difficile le suivi d'une erreur d'impression suivante:

my_object.my_atttr = 4.

Si votre objet est un algorithme compliqué, vous passerez un certain temps à essayer de comprendre pourquoi il ne converge pas (remarquez un "t" supplémentaire dans la ligne ci-dessus)

2) Un passeur peut parfois évoluer vers une méthode complexe et lente (par exemple, frapper une base de données). Il serait très difficile pour un autre développeur de comprendre pourquoi la fonction suivante est très lente. Il pourrait passer beaucoup de temps sur le profilage de la méthode do_something() alors que my_object.my_attr = 4. est en réalité la cause du ralentissement:

def slow_function(my_object):
    my_object.my_attr = 4.
    my_object.do_something()
5
otognan

_@property_ et les getters et setters traditionnels ont leurs avantages. Cela dépend de votre cas d'utilisation.

Avantages de _@property_

  • Il n'est pas nécessaire de modifier l'interface en modifiant l'implémentation de l'accès aux données. Lorsque votre projet est petit, vous souhaiterez probablement utiliser un accès d'attribut direct pour accéder à un membre de la classe. Par exemple, supposons que vous ayez un objet foo de type Foo, qui a un membre num. Ensuite, vous pouvez simplement obtenir ce membre avec _num = foo.num_. Au fur et à mesure que votre projet se développe, vous pouvez avoir besoin de vérifier ou de déboguer l'accès simple aux attributs. Ensuite, vous pouvez le faire avec un _@property_ dans de la classe. L'interface d'accès aux données reste la même, de sorte qu'il n'est pas nécessaire de modifier le code client.

    Cité de PEP-8 :

    Pour les attributs de données publics simples, il est préférable d’exposer uniquement le nom de l’attribut, sans méthodes compliquées d’accesseur/mutateur. Gardez à l'esprit que Python fournit un moyen simple d'améliorer les améliorations futures, si vous estimez qu'un attribut de données simple doit développer son comportement fonctionnel. Dans ce cas, utilisez des propriétés pour masquer l'implémentation fonctionnelle derrière la syntaxe d'accès aux attributs de données simples.

  • L'utilisation de _@property_ pour l'accès aux données dans Python est considérée comme Pythonic :

    • Il peut renforcer votre auto-identification en tant que programmeur Python (et non Java).

    • Votre entretien d'embauche peut être utile si votre interlocuteur pense que les getters et les setters de style Java sont anti-patterns .

Avantages des getter et setters traditionnels

  • Les getters et les setters traditionnels permettent un accès aux données plus compliqué qu'un simple accès aux attributs. Par exemple, lorsque vous définissez un membre du groupe, vous avez parfois besoin d'un indicateur indiquant l'endroit où vous souhaitez forcer cette opération même si quelque chose ne semble pas parfait. Bien qu'il ne soit pas évident d'augmenter l'extension d'un accès direct au membre tel que _foo.num = num_, vous pouvez facilement augmenter votre setter traditionnel avec un paramètre supplémentaire force:

    _def Foo:
        def set_num(self, num, force=False):
            ...
    _
  • Les getters et les setters traditionnels expliquent qu'un accès membre de la classe se fait par le biais d'une méthode. Ça signifie:

    • Le résultat obtenu peut ne pas être identique à ce qui est exactement stocké dans cette classe.

    • Même si l'accès ressemble à un simple accès attributaire, les performances peuvent varier considérablement.

    À moins que les utilisateurs de la classe s'attendent à ce que _@property_ se cache derrière chaque instruction d'accès aux attributs, rendre ces informations explicites peut aider à minimiser les surprises de vos utilisateurs.

  • Comme mentionné par @ NeilenMarais et dans cet article , il est plus facile d'étendre les accesseurs et les setters traditionnels dans des sous-classes que d'étendre les propriétés.

  • Les getters et les setters traditionnels sont largement utilisés depuis longtemps dans différentes langues. Si vous avez des personnes d'horizons différents dans votre équipe, elles ont l'air plus familières que _@property_. De plus, à mesure que votre projet se développe, si vous deviez migrer de Python vers une autre langue qui n'a pas _@property_, l'utilisation des accesseurs et des régulateurs traditionnels faciliterait la migration.

Mises en garde

  • Ni _@property_ ni les getters et les setters traditionnels ne rendent le membre du groupe privé, même si vous utilisez un double soulignement (soulignement double) avant son nom:

    _class Foo:
        def __init__(self):
            self.__num = 0
    
        @property
        def num(self):
            return self.__num
    
        @num.setter
        def num(self, num):
            self.__num = num
    
        def get_num(self):
            return self.__num
    
        def set_num(self, num):
            self.__num = num
    
    foo = Foo()
    print(foo.num)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0
    _
3
Cyker