2
votes

Différencier l'accès à l'attribut de classe depuis la classe (ou le fichier) et depuis l'extérieur en python

Prenons un exemple de classe Foo en Python:

class Foo:
    bar = 'bar'
    def access_bar(self):
        return self.bar  

Puis-je, par exemple afficher un avertissement, en accédant directement à Foo (). bar , mais en même temps, ne pas imprimer cet avertissement lors de l'appel de Foo (). access_bar () , qui accède à cet attribut depuis la classe?

J'ai essayé d'implémenter __getattribute __ , mais je n'ai pas réussi à différencier ces cas.

Je sais que c'est une question assez étrange, mais ne me répondez pas comme "Vous ne devriez pas avoir besoin de ça".


3 commentaires

Foo (). access_bar () n'est pas la même chose que Foo.access_bar ()


Vous semblez avoir une motivation pour poser votre question. Cela pourrait être utile si vous expliquez pourquoi cette distinction est nécessaire (même si ce n'est que de la curiosité!)


Voulez-vous vraiment un attribut class par opposition à un attribut d'instance ( self.bar = 'bar' )? Êtes-vous sûr de connaître la différence?


4 Réponses :


2
votes

vous pourriez faire de bar une propriété qui permet de contrôler l'accès sans montrer l'appel de méthode à l'extérieur, et rendre votre attribut privé:

direct access
warn bar
OK bar

imprime:

class Foo:
    __bar = 'bar'
    @property
    def bar(self):
        print("direct access")
        return Foo.__bar
    def access_bar(self):
        return self.__bar

f = Foo()

print("warn",f.bar)
print("OK",f.access_bar())


4 commentaires

Je sais de cette façon. Mais j'accède beaucoup à la `__bar` de l'intérieur et je n'ai pas aimé les traits de soulignement (et les avertissements PyCharm sur leur utilisation). J'essaie donc d'implémenter une manière dont je n'ai pas à utiliser de traits de soulignement à l'intérieur de la classe, mais l'utilisateur est averti lorsqu'il essaie d'accéder à cet attribut de l'extérieur.


@JosefKvita gardez à l'esprit que il n'y a pas de variables de classe privée en python . Peu importe combien vous essayez de cacher la variable interne qui contient la valeur, quelqu'un peut trouver un moyen d'y accéder directement. S'il s'agit d'une tentative pour dissuader d'autres ingénieurs d'accéder à des champs sensibles, essayez peut-être d'avoir une conversation avec eux à la place :)


Je le sais. Je veux juste imprimer un avertissement, pas le rendre inaccessible. :)


@JosefKvita pourquoi accédez-vous beaucoup à __bar de l'intérieur? Le seul accès direct ne devrait-il pas provenir de access_bar ? Sinon, ma réponse ne vous aidera pas, elle ne fait pas la différence entre l'intérieur et l'extérieur de la classe.



1
votes

Je suggère de stocker la valeur dans un attribut protégé (un trait de soulignement) ou privé (deux traits de soulignement), et de faire de bar une propriété accessible en toute sécurité, l'équivalent de access_bar dans votre question. C'est ainsi que ce genre de chose est généralement fait en Python.

class Foo:
    _bar = 'bar'

    @property
    def bar(self):
        # do extra things here
        return self._bar

Un utilisateur peut toujours écrire foo._bar ou foo._Foo__bar ( pour un attribut privé) pour obtenir l'attribut en externe sans aucun avertissement, mais s'ils sont conscients des conventions entourant les traits de soulignement de début, ils se sentiront probablement un peu mal à l'aise et seront conscients des risques.


0 commentaires

2
votes

Voici la "vraie" réponse à votre question, que vous ne devriez probablement pas faire:

import inspect


class Foo:
    bar = 'bar'

    def access_bar(self):
        return self.bar

    def __getattribute__(self, item):
        if item == 'bar':
            code = inspect.currentframe().f_back.f_code
            if not (start_lineno <= code.co_firstlineno <= end_lineno
                    and code.co_filename == __file__):
                print('Warning: accessing bar directly')
        return super().__getattribute__(item)


lines, start_lineno = inspect.getsourcelines(Foo)
end_lineno = start_lineno + len(lines) - 1

print(1, Foo().bar)
print(2, Foo().access_bar())

Si vous faites cela, il est important qu'il n'y ait qu'une seule classe nommée Foo dans le fichier, sinon inspect.getsourcelines (Foo) peut ne pas donner le bon résultat.


5 commentaires

print (Foo.bar) n'imprime pas d'avertissement. Cela ne fonctionne que sur les instances de la classe Foo . Vous aurez besoin d'une métaclasse si vous souhaitez que cela fonctionne également pour les attributs de classe.


Je pense que c'est exactement ce dont j'avais besoin, merci beaucoup! J'essaierai de jouer avec. Je sais qu'il devait y avoir quelque chose, car tout peut être fait en Python! :-)


bon appel, je n'ai même pas osé dire à l'OP d'utiliser le module inspect pour cela. "ce que vous ne devriez probablement pas faire" vous donne mon vote :)


Maintenant, il vérifie si l'attribut a été appelé depuis l'extérieur de la méthode access_bar (), que se passe-t-il si je voulais vérifier en dehors de la classe Foo (la barre accessible dans n'importe quelle méthode à l'intérieur de la classe n'afficherait pas l'avertissement)?


Pourquoi ne pas utiliser inspect pour trouver l'origine de l'appel à __getattribute__ , le modifier légèrement pour qu'il apprenne à imprimer un avertissement? Je n'ai aucune idée à quel point c'est une bonne idée.



1
votes

Voici une autre tentative pour améliorer la réponse d'Alex en ajoutant une métaclasse afin qu'elle fonctionne également pour les attributs de classe, et en supprimant avec le module inspect , et ajoutez à la place un indicateur d'avertissement à la fonction __getattribute__ elle-même.

class FooType(type):
    def __getattribute__(self, item):
        if item == "bar":
            print("Warning: accessing bar directly from class")
        return item.__getattribute__(self, item)

class Foo(object, metaclass=FooType):
    bar = 'bar'

    def access_bar(self):
        return self.__getattribute__('bar', warn=False)

    def __getattribute__(self, item, warn=True):
        if item == 'bar' and warn:
            print('Warning: accessing bar directly from instance')
        return super().__getattribute__(item)


print(Foo.bar)
#Warning: accessing bar directly from class
#bar

print(Foo().bar)
#Warning: accessing bar directly from instance
#bar

print(Foo().access_bar())
#bar


2 commentaires

La mauvaise chose à ce sujet est que je dois accéder à l'attribut partout dans la classe (comme sur 100 endroits), avec un self .__ getattribute __ ('bar', warn = False) , pas seulement self.bar , ou ai-je tort? C'est sûrement pire que self._bar .


C'est vrai. Je pense que cette solution vous empêche d'utiliser instance.bar partout, y compris à l'intérieur de la classe. Il vous encourage à remplacer self.bar par self.access_bar () .