web-dev-qa-db-fra.com

Qu'est-ce que getattr () exactement et comment puis-je l'utiliser?

J'étais en train de lire à propos de la fonction getattr() . Le problème est que je ne peux toujours pas saisir l'idée de son utilisation. La seule chose que je comprends à propos de getattr() est que getattr(li, "pop") est identique à appeler li.pop.

Je ne comprenais pas quand le livre mentionnait comment vous l'utilisiez pour obtenir une référence à une fonction sans connaître son nom jusqu'à l'exécution. Peut-être que c'est moi qui suis un noob en programmation, en général. Quelqu'un pourrait-il éclairer un peu le sujet? Quand et comment l'utiliser exactement?

226
Terence Ponce

Vous pouvez voir un exemple complet ici:

L'introspection peut être utilisée à différentes fins. Celle présentée dans 'Dive Into Python' est simplement un moyen d'ajouter des fonctionnalités (plug-in) de manière dynamique à votre application.

Par dynamiquement, j'entends sans faire de modification dans l'application principale pour ajouter une nouvelle fonctionnalité.

En prenant l'exemple 'Dive Into Python' - une application simple pour extraire un attribut d'un fichier de fichier différent _ - vous pouvez ajouter le traitement d'un nouveau format de fichier sans modifier l'application d'origine.

Je vous recommande de finir le livre. Au fur et à mesure que vous lirez, tout deviendra de plus en plus clair.

58
Alois Cochard

Les objets en Python peuvent avoir des attributs - des attributs de données et des fonctions avec lesquelles ils peuvent travailler (méthodes). En fait, chaque objet a des attributs intégrés.

Par exemple, vous avez un objet person, qui a plusieurs attributs: name, gender, etc.

Vous accédez à ces attributs (qu’il s’agisse de méthodes ou d’objets de données) en écrivant habituellement: person.name, person.gender, person.the_method(), etc.

Mais que se passe-t-il si vous ne connaissez pas le nom de l'attribut au moment où vous écrivez le programme? Par exemple, le nom de l'attribut est stocké dans une variable appelée attr_name.

si

attr_name = 'gender'

alors, au lieu d'écrire

gender = person.gender

tu peux écrire

gender = getattr(person, attr_name)

Quelques pratiques:

Python 3.4.0 (default, Apr 11 2014, 13:05:11)

>>> class Person():
...     name = 'Victor'
...     def say(self, what):
...         print(self.name, what)
... 
>>> getattr(Person, 'name')
'Victor'
>>> attr_name = 'name'
>>> person = Person()
>>> getattr(person, attr_name)
'Victor'
>>> getattr(person, 'say')('Hello')
Victor Hello

getattr lève AttributeError si l'attribut avec le nom donné n'existe pas dans l'objet:

>>> getattr(person, 'age')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute 'age'

Mais vous pouvez passer une valeur par défaut comme troisième argument, qui sera retourné si cet attribut n'existe pas:

>>> getattr(person, 'age', 0)
0

Vous pouvez utiliser getattr avec dir pour effectuer une itération sur tous les noms d'attributs et obtenir leurs valeurs:

>>> dir(1000)
['__abs__', '__add__', ..., '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

>>> obj = 1000
>>> for attr_name in dir(obj):
...     attr_value = getattr(obj, attr_name)
...     print(attr_name, attr_value, callable(attr_value))
... 
__abs__ <method-wrapper '__abs__' of int object at 0x7f4e927c2f90> True
...
bit_length <built-in method bit_length of int object at 0x7f4e927c2f90> True
...

>>> getattr(1000, 'bit_length')()
10

Une utilisation pratique pour cela serait de trouver toutes les méthodes dont le nom commence par test et appelez-les .

Semblable à getattr il y a setattr qui vous permet de définir un attribut d'un objet ayant son nom:

>>> setattr(person, 'name', 'Andrew')
>>> person.name  # accessing instance attribute
'Andrew'
>>> Person.name  # accessing class attribute
'Victor'
>>>
248
warvariuc

Pour moi, getattr est le plus facile à expliquer de cette façon:

Il vous permet d'appeler des méthodes en fonction du contenu d'une chaîne au lieu de saisir le nom de la méthode.

Par exemple, vous ne pouvez pas faire ceci:

obj = MyObject()
for x in ['foo', 'bar']:
    obj.x()

parce que x n'est pas du type "builtin", mais "str". Cependant, vous POUVEZ faire ceci:

obj = MyObject()
for x in ['foo', 'bar']:
    getattr(obj, x)()

Il vous permet de vous connecter dynamiquement à des objets en fonction de votre saisie. Je l'ai trouvé utile lorsqu'il s'agit d'objets et de modules personnalisés.

83
NuclearPeon

Un cas d'utilisation assez courant pour getattr est le mappage de données vers des fonctions. 

Par exemple, dans un environnement Web tel que Django ou Pylons, getattr facilite la mise en correspondance de l'URL d'une demande Web avec la fonction qui va le gérer. Si vous regardez sous le capot du routage de Pylons, par exemple, vous verrez que (par défaut, au moins), il tronque l'URL d'une requête, comme:

http://www.example.com/customers/list

dans "clients" et "liste". Ensuite, il recherche une classe de contrôleur nommée CustomerController. En supposant qu'il trouve la classe, il crée une instance de la classe puis utilise getattr pour obtenir sa méthode list. Il appelle ensuite cette méthode en lui transmettant la requête en tant qu'argument.

Une fois que vous avez compris cette idée, il devient très facile d'étendre les fonctionnalités d'une application Web: ajoutez simplement de nouvelles méthodes aux classes de contrôleur, puis créez des liens dans vos pages en utilisant les URL appropriées pour ces méthodes. Tout cela est rendu possible par getattr.

43
Robert Rossney

Voici un exemple rapide et erroné de la façon dont une classe peut déclencher différentes versions d'une méthode de sauvegarde en fonction du système d'exploitation sur lequel elle est exécutée à l'aide de getattr().

import os

class Log(object):
    def __init__(self):
        self.os = os.name
    def __getattr__(self, name):
        """ look for a 'save' attribute, or just 
          return whatever attribute was specified """
        if name == 'save':
            try:
                # try to dynamically return a save 
                # method appropriate for the user's system
                return getattr(self, self.os)
            except:
                # bail and try to return 
                # a default save method
                return getattr(self, '_save')
        else:
            return getattr(self, name)

    # each of these methods could have save logic specific to 
    # the system on which the script is executed
    def posix(self): print 'saving on a posix machine'
    def nt(self): print 'saving on an nt machine'
    def os2(self): print 'saving on an os2 machine'
    def ce(self): print 'saving on a ce machine'
    def Java(self): print 'saving on a Java machine'
    def riscos(self): print 'saving on a riscos machine'
    def _save(self): print 'saving on an unknown operating system'

    def which_os(self): print os.name

Maintenant, utilisons cette classe dans un exemple:

logger = Log()

# Now you can do one of two things:
save_func = logger.save
# and execute it, or pass it along 
# somewhere else as 1st class:
save_func()

# or you can just call it directly:
logger.save()

# other attributes will hit the else 
# statement and still work as expected
logger.which_os()
12
Josh

Outre toutes les réponses étonnantes ici, il existe un moyen d'utiliser getattr pour enregistrer des lignes de code copieuses et les maintenir bien ajustées. Cette pensée est venue après la représentation épouvantable de code qui pouvait parfois être une nécessité.

Scénario

Supposons que votre structure de répertoire est la suivante:

- superheroes.py
- properties.py

Et vous disposez de fonctions pour obtenir des informations sur Thor, Iron Man, Doctor Strange dans superheroes.py. Vous écrivez très intelligemment toutes leurs propriétés dans properties.py dans une variable dict, puis vous y accédez.

properties.py

thor = {
    'about': 'Asgardian god of thunder',
    'weapon': 'Mjolnir',
    'powers': ['invulnerability', 'keen senses', 'vortex breath'], # and many more
}
iron_man = {
    'about': 'A wealthy American business magnate, playboy, and ingenious scientist',
    'weapon': 'Armor',
    'powers': ['intellect', 'armor suit', 'interface with wireless connections', 'money'],
}
doctor_strange = {
    'about': ' primary protector of Earth against magical and mystical threats',
    'weapon': 'Magic',
    'powers': ['magic', 'intellect', 'martial arts'],
}

Supposons maintenant que vous souhaitiez rendre les capacités de chacun d’entre elles à la demande dans superheroes.py. Donc, il y a des fonctions comme

from .properties import thor, iron_man, doctor_strange


def get_thor_weapon():
    return thor['weapon']


def get_iron_man_bio():
    return iron_man['about']


def get_thor_powers():
    return thor['powers']

... et plus de fonctions renvoyant différentes valeurs en fonction des clés et du super-héros.

Avec l’aide de getattr, vous pouvez faire quelque chose comme:

from . import properties


def get_superhero_weapon(hero):
    superhero = getattr(properties, hero)
    return superhero['weapon']


def get_superhero_powers(hero):
    superhero = getattr(properties, hero)
    return superhero['powers']

Vous avez considérablement réduit le nombre de lignes de code, les fonctions et la répétition!

Oh, et bien sûr, si vous avez de mauvais noms comme properties_of_thor pour les variables, vous pouvez les créer et y accéder en faisant simplement

def get_superhero_weapon(hero):
    superhero = 'properties_of_{}'.format(hero)
    all_properties = getattr(properties, superhero)
    return all_properties['weapon']

REMARQUE: pour ce problème particulier, il peut exister des moyens plus intelligents de gérer la situation, mais l’idée est de donner un aperçu de l’utilisation de getattr au bon endroit pour écrire du code plus propre.

6
unixia

J'utilise parfois getattr(..) pour initialiser paresseusement des attributs d'importance secondaire juste avant qu'ils ne soient utilisés dans le code. 

Comparez ce qui suit:

class Graph(object):
    def __init__(self):
        self.n_calls_to_plot = 0

    #...
    #A lot of code here
    #...

    def plot(self):
        self.n_calls_to_plot += 1

Pour ça:

class Graph(object):
    def plot(self):
        self.n_calls_to_plot = 1 + getattr(self, "n_calls_to_plot", 0)

L’avantage de la seconde méthode est que n_calls_to_plot apparaît uniquement autour de la place dans le code où il est utilisé. Ceci est bon pour la lisibilité, parce que (1) vous pouvez voir immédiatement quelle valeur il commence en lisant comment il est utilisé, (2) cela n'introduit pas de distraction dans la méthode __init__(..), qui devrait idéalement concerner l’état conceptuel du classe, plutôt qu’un compteur d’utilité qui n’est utilisé que par l’une des méthodes de la fonction pour des raisons techniques, telles que l’optimisation, et n’a rien à voir avec la signification de l’objet.

3
Evgeni Sergeev

Très souvent, lorsque je crée un fichier XML à partir de données stockées dans une classe, je recevais souvent des erreurs si l'attribut n'existait pas ou était de type None. Dans ce cas, mon problème ne connaissait pas le nom de l'attribut, comme indiqué dans votre question, mais les données étaient-elles jamais stockées dans cet attribut.

class Pet:
    def __init__(self):
        self.hair = None
        self.color = None

Si j’utilisais hasattr pour cela, il renverrait True même si la valeur de l’attribut était de type None et la commande ElementTree set échouait.

hasattr(temp, 'hair')
>>True

Si la valeur de l'attribut était de type None, getattr le renverrait également, ce qui entraînerait l'échec de ma commande ElementTree set

c = getattr(temp, 'hair')
type(c)
>> NoneType

J'utilise la méthode suivante pour prendre en charge ces cas maintenant:

def getRealAttr(class_obj, class_attr, default = ''):
    temp = getattr(class_obj, class_attr, default)
    if temp is None:
        temp = default
    Elif type(temp) != str:
        temp = str(temp)
    return temp

C’est quand et comment j’utilise getattr.

3
btathalon

getattr(object, 'x') est équivalent à object.x.

Il y a deux cas getattr peut être utile.

  • vous ne pouvez pas écrire object.x, (car vous ne savez pas à l'avance quel attribut vous voulez, par exemple: il provient d'une chaîne)
  • vous voulez fournir une valeur par défaut. object.y lèvera une AttributeError s'il n'y a pas de y. Mais getattr(object, 'y', 5) renverra 5.
2
blue_note

Une autre utilisation de getattr () pour implémenter une instruction switch en Python. Il utilise les deux réflexions pour obtenir le type de cas.

import sys

class SwitchStatement(object):
    """ a class to implement switch statement and a way to show how to use gettattr in Pythion"""

    def case_1(self):
        return "value for case_1"

    def case_2(self):
        return "value for case_2"

    def case_3(self):
        return "value for case_3"

    def case_4(self):
        return "value for case_4"

    def case_value(self, case_type=1):
        """This is the main dispatchmethod, that uses gettattr"""
        case_method = 'case_' + str(case_type)
        # fetch the relevant method name
        # Get the method from 'self'. Default to a lambda.
        method = getattr(self, case_method, lambda: "Invalid case type")
        # Call the method as we return it
        return method()

def main(_):
    switch = SwitchStatement()
    print swtich.case_value(_)

if __== '__main__':
    main(int(sys.argv[1]))
2
Jules Damji
# getattr

class hithere():

    def french(self):
        print 'bonjour'

    def english(self):
        print 'hello'

    def german(self):
        print 'hallo'

    def czech(self):
        print 'ahoj'

    def noidea(self):
        print 'unknown language'


def dispatch(language):
    try:
        getattr(hithere(),language)()
    except:
        getattr(hithere(),'noidea')()
        # note, do better error handling than this

dispatch('french')
dispatch('english')
dispatch('german')
dispatch('czech')
dispatch('spanish')
2
Kduyehj

Il clarifie également de https://www.programiz.com/python-programming/methods/built-in/getattr

class Person:
    age = 23
    name = "Adam"

person = Person()
print('The age is:', getattr(person, "age"))
print('The age is:', person.age)

L'âge est: 23

L'âge est: 23

class Person:
    age = 23
    name = "Adam"

person = Person()

# when default value is provided
print('The sex is:', getattr(person, 'sex', 'Male'))

# when no default value is provided
print('The sex is:', getattr(person, 'sex'))

Le sexe est: masculin

AttributeError: l'objet 'Person' n'a pas d'attribut 'sex'

0
Barny