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?
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.
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'
>>>
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.
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
.
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()
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.
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.
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
.
getattr(object, 'x')
est équivalent à object.x
.
Il y a deux cas où getattr
peut être utile.
object.x
, (car vous ne savez pas à l'avance quel attribut vous voulez, par exemple: il provient d'une chaîne)object.y
lèvera une AttributeError
s'il n'y a pas de y
. Mais getattr(object, 'y', 5)
renverra 5
.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]))
# 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')
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'