2009-05-04 8 views
16

Je veux créer un objet en python qui a quelques attributs et je veux me protéger d'utiliser accidentellement le mauvais nom d'attribut. Le code est le suivant:python, __slots__, et "attribut est en lecture seule"

class MyClass(object) : 
    m = None # my attribute 
    __slots__ = ("m") # ensure that object has no _m etc 

a = MyClass() # create one 
a.m = "?" # here is a PROBLEM 

Mais après l'exécution de ce code simple, je reçois une erreur très étrange:

Traceback (most recent call last): 
    File "test.py", line 8, in <module> 
    a.m = "?" 
AttributeError: 'test' object attribute 'm' is read-only 

Yat-il un programmeur sage qui peut épargner un peu de leur temps et me éclairer à propos des erreurs "en lecture seule"?

+1

Cela ressemble à un cas d'utilisation inutile. Pourquoi fais-tu ça? Pourquoi n'utilisez-vous pas les propriétés? –

+0

Je veux me pro- fiter d'écrire un nom de propriété avec une faute de frappe, pour eample si j'écris object.my_prperty = 1 au lieu de object.my_property = 1 (manqué 'o'), python ne montrera aucune erreur mais j'aurai un erreur de logique dans mon programme. Donc, je veux limiter une propriété à mon ensemble prédéfini, donc seulement ils peuvent être acessed. Comment je peux résoudre cela avec des propriétés? – grigoryvp

+9

généralement en python, nous acceptons la possibilité de fautes d'orthographe dans les affectations et écrit de bons tests qui captureraient aussi des erreurs superficielles et plus sémantiques. Je suggère humblement que si vous voulez écrire python, vous devriez envisager de le faire en python au lieu de lutter contre la langue dentaire. J'ajouterai que les fautes d'orthographe dans mon code python m'ont coûté * peut-être * 20 minutes de temps au cours des 9 dernières années. – llimllib

Répondre

38

Lorsque vous déclarez des variables d'instance à l'aide __slots__, Python crée une descriptor object comme une variable de classe avec le même nom. Dans votre cas, ce descripteur est écrasé par la variable de classe m que vous définissez à la ligne suivante:

m = None # my attribute 

Voici ce que vous devez faire: Ne pas définir une variable de classe appelée m et initialiser l'instance variable m dans la méthode __init__. En note, les tuples avec des éléments simples ont besoin d'une virgule après l'élément. Les deux fonctionnent dans votre code car __slots__ accepte une chaîne unique ou une itération/séquence de chaînes. En général, pour définir un tuple contenant l'élément 1, utilisez (1,) ou 1, et non (1).

+0

Savez-vous pourquoi, si je l'ai écrit: __slots__ = [ "m"] m = 5 Python écrasera objet descripteur avec variable et n'utilisera pas la méthode set() des objets descripteurs pour définir la valeur à la place? – grigoryvp

+7

Parce que m dans ce contexte est une variable de classe, pas une variable d'instance. Pour utiliser la méthode set() du descripteur, vous devez affecter le m d'un objet, pas une classe. Si vous voulez initialiser une variable d'instance d'un objet, faites-le depuis la méthode __init__ comme je l'ai fait dans mon fragment de code. –

+0

La clé de la docs (qui btw déplacé [ici] (https://docs.python.org/2/reference/datamodel.html#slots)) est '__slots__ sont implémentées au niveau de la classe en créant des descripteurs pour chaque variable prénom. Par conséquent, les attributs de classe ne peuvent pas être utilisés pour définir des valeurs par défaut pour les variables d'instance définies par __slots__; sinon, l'attribut de classe remplacerait l'affectation des descripteurs ». Alors que se passe-t-il (indépendamment de l'ordre de définition de 'm' et' __slots__'?) Lorsque l'assignation est tentée, la méthode set n'est plus mappée à quelque chose - ni get mais elle revient à MyClass.m –

7

__slots__ fonctionne avec des variables d'instance, alors que ce que vous avez là est une variable de classe. Voici comment vous devriez le faire:

class MyClass(object) : 
    __slots__ = ("m",) 
    def __init__(self): 
    self.m = None 

a = MyClass() 
a.m = "?"  # No error 
6

Considérez ceci.

class SuperSafe(object): 
    allowed= ("this", "that") 
    def __init__(self): 
     self.this= None 
     self.that= None 
    def __setattr__(self, attr, value): 
     if attr not in self.allowed: 
      raise Exception("No such attribute: %s" % (attr,)) 
     super(SuperSafe, self).__setattr__(attr, value) 

Une meilleure approche consiste à utiliser des tests unitaires pour ce type de vérification. C'est une bonne quantité de frais généraux d'exécution.

+4

+1 pour pas abuser __slots__ – Ravi

+3

Mais c'est plus de lignes de code par rapport à __slots__? Pourquoi vérifier manuellement ce qui peut être automatiquement effectué par langue. – grigoryvp

+3

Pour éviter les messages "d'erreur très étrange" et pour éviter d'avoir à passer beaucoup de temps à déboguer les mystères intérieurs de __slots__. Je préfère des solutions simples et évidentes où la connaissance secrète n'est pas requise pour déboguer. –

14

Vous utilisez complètement le code __slots__. Il empêche la création de __dict__ pour les instances. Cela n'a de sens que si vous rencontrez des problèmes de mémoire avec de nombreux petits objets, car se débarrasser de __dict__ peut réduire l'empreinte. C'est une optimisation inconditionnelle qui n'est pas nécessaire dans 99,9% des cas.

Si vous avez besoin du type de sécurité que vous avez décrit alors Python est vraiment la mauvaise langue. Mieux vaut utiliser quelque chose de strict comme Java (au lieu d'essayer d'écrire Java en Python).

Si vous ne parvenez pas à comprendre pourquoi les attributs de classe ont provoqué ces problèmes dans votre code, vous devriez peut-être réfléchir à deux fois avant d'introduire des hacks de langage comme celui-ci. Il serait probablement plus sage de se familiariser d'abord avec la langue. Pour plus d'informations, voici le documentation link for slots.

+4

+1 pour l'exhaustivité. Comme mentionné partout, __slots__ n'est pas une mauvaise idée, mais nécessite une utilisation prudente. C'est plus qu'une vérification de sécurité et ajoute beaucoup de complexité à l'implémentation qui exigera de prêter attention à la façon dont vous utilisez l'objet. –

0
class MyClass(object) : 
    m = None # my attribute 

m Voici les attributs de classe, plutôt que l'attribut d'instance. Vous devez le connecter avec votre instance par vous-même au __init__.

Questions connexes