2009-07-19 8 views
2

En Python, il existe des exemples de classes intégrées avec des instances fortement contraintes. Par exemple, "None" est la seule instance de sa classe, et dans la classe bool, il n'y a que deux objets, "True" et "False" (j'espère que je suis plus ou moins correct jusqu'ici).Comment créer des classes en Python avec des instances hautement contraintes

Un autre bon exemple sont les entiers: si a et b sont des instances du type int alors a == b implique que a est b.

Deux questions:

  1. Comment peut-on créer une classe avec des instances de la même contrainte? Par exemple, nous pourrions demander une classe avec exactement 5 instances. Ou il peut y avoir une infinité d'instances, comme type int, mais elles ne sont pas arbitraires.

  2. Si les entiers forment une classe, pourquoi int() donne l'instance 0? compare ceci à une classe définie par l'utilisateur Cl, où Cl() donnerait une instance de la classe, pas une instance unique spécifique, comme 0. Ne devrait pas renvoyer int() un objet entier non spécifié, c'est-à-dire un entier sans valeur spécifiée?

+0

En Python, deux entiers avec la même valeur ne sont pas nécessairement la même instance (voir la réponse de Roberto Liffredo ci-dessous). –

Répondre

5

Vous parlez de donner une sémantique de valeur classe , qui est habituellement fait en créant des instances de classe de façon normale, mais se souvenant de chacun, et si une instance correspondante serait créée, donnez plutôt l'instance déjà créée. En python, ceci peut être réalisé en surchargeant les classes __new__method. Bref exemple, disons que nous voulions utiliser des paires d'entiers pour représenter les coordonnées, et avoir la sémantique de valeur correcte.

class point(object): 
    memo = {} 
    def __new__(cls, x, y): 
     if (x, y) in cls.memo:   # if it already exists, 
      return cls.memo[(x, y)] # return the existing instance 
     else:       # otherwise, 
      newPoint = object.__new__(cls) # create it, 
      newPoint.x = x    # initialize it, as you would in __init__ 
      newPoint.y = y    
      cls.memo[(x, y)] = newPoint # memoize it, 
      return newPoint   # and return it! 

+0

Merci, c'est très bien. Je devais modifier une petite chose: sur la ligne 7 je devais mettre: newPoint = objet .__ nouveau __ (cls) parce que je recevais erreur ce point n'est pas un sous-type de super. Je ne suis pas si familier avec les super classes, comprenez-vous pourquoi c'est une erreur? – Marco

+0

Cela n'a pas fonctionné pour vous parce que c'était faux, merci d'avoir réparé mon bug! aussi, édité mon message pour refléter le résultat correct. – SingleNegationElimination

1

Je pense que certains noms pour le concept que vous pensez à interning sont et immutable objects. Comme réponse à vos questions spécifiques, je pense que pour # 1, vous pouvez rechercher votre instance contrainte dans une méthode de classe et le renvoyer.

Pour la question 2, je pense que c'est juste une question de comment vous spécifiez votre classe. Une instance non spécifique de la classe int serait plutôt inutile, donc spécifiez-la donc il est impossible de la créer.

3

Pour la première question, vous pouvez implémenter un modèle de conception de classe singleton http://en.wikipedia.org/wiki/Singleton_pattern dont vous devez limiter le nombre d'instances.

Pour la deuxième question, je pense que cela explique type de votre problème http://docs.python.org/library/stdtypes.html

Parce que les entiers sont des types, il y a des limites à elle.

Voici une autre ressource ... http://docs.python.org/library/functions.html#built-in-funcs

2
  1. les créer à l'avance et le retour de ceux de __new__ au lieu de créer un nouvel objet ou cache les instances créées (weakrefs sont ici à portée de main) et retourner un de ceux au lieu de créer une nouvelle objet.
  2. Les entiers sont spéciaux.En pratique, cela signifie que vous ne pouvez jamais utiliser l'identité pour les comparer car vous utiliseriez l'identité pour comparer d'autres objets. Puisqu'ils sont immuables et rarement utilisés d'une manière autre qu'un contexte de valeur, ceci n'est pas vraiment un problème. Cela est fait, pour autant que je sache, pour des raisons de mise en œuvre plus que toute autre chose. (Et comme il n'y a pas d'indication claire que c'est une mauvaise façon, c'est une bonne décision.)

singletons tels que None: Créer la classe avec le nom que vous voulez donner la variable, et REBIND la variable à l'instance (uniquement) ou supprimez la classe par la suite. C'est pratique lorsque vous voulez émuler une interface telle que getattr, où un paramètre est facultatif mais l'utilisation de None est différente de ne pas fournir de valeur.

class raise_error(object): pass 
raise_error = raise_error() 

def example(d, key, default=raise_error): 
    """Return d[key] if key in d, else return default or 
    raise KeyError if default isn't supplied.""" 
    try: 
    return d[key] 
    except KeyError: 
    if default is raise_error: 
     raise 
    return default 
4

Ressemble # 1 a été bien déjà répondu et je veux juste expliquer un principe lié à # 2, qui semble avoir été oublié par tous les répondants: pour la plupart construits dans les types, appeler le type sans paramètres (le "constructeur par défaut") renvoie une instance de ce type qui évalue comme faux. Cela signifie un conteneur vide pour les types de conteneur, un nombre qui se compare à zéro pour les types de numéro.

>>> import decimal 
>>> decimal.Decimal() 
Decimal("0") 
>>> set() 
set([]) 
>>> float() 
0.0 
>>> tuple() 
() 
>>> dict() 
{} 
>>> list() 
[] 
>>> str() 
'' 
>>> bool() 
False 

Voir? Assez régulier en effet! De plus, pour les types mutables, comme la plupart des conteneurs, l'appel du type retourne toujours une nouvelle instance; pour les types immuables, comme les nombres et les chaînes, peu importe (c'est une optimisation interne possible pour renvoyer une nouvelle référence à une instance immutable existante, mais la mise en oeuvre n'est pas nécessaire pour effectuer une telle optimisation, et si elle le fait les exécuter assez sélectivement) car il n'est jamais correct de comparer des instances de type immuable avec is ou de manière équivalente par id().

Si vous concevez un type dont certains cas peuvent évaluer comme fausse (en ayant __len__ ou __nonzero__ méthodes spéciales dans la classe), il est conseillé de suivre le même précepte (ont __init__ [ou __new__ pour immutables], si elle est appelée sans arguments [[au-delà de self pour __init__ et 'cls' pour __new__ bien sûr]], préparer une instance [[new, if mutable]] "empty" ou "zero-like" de la classe).

2

Pour répondre à la question plus générique de la création d'instances contraintes, cela dépend de la contrainte. Les deux exemples ci-dessus sont une sorte de "singletons", bien que le second exemple soit une variation où vous pouvez avoir plusieurs instances d'une classe, mais vous n'en aurez qu'une par valeur d'entrée.

Ceux-ci peuvent aussi bien fait en redéfinissant la méthode de classe __new__, de sorte que la classe crée les instances si elle n'a pas déjà été créés, et les renvoie, et stocke comme un attribut de la classe (comme a été suggéré ci-dessus). Cependant, un moyen un peu moins hackish est d'utiliser des métaclasses. Ce sont des classes qui modifient le comportement des classes, et singletons est un excellent exemple de quand utiliser les métaclasses. Et la grande chose à ce sujet est que vous pouvez réutiliser les métaclasses. En créant une métaclasse Singleton, vous pouvez ensuite utiliser cette métaclasse pour tous les singletons que vous avez.

Un bel exemple Python dans Wikipedia: http://en.wikipedia.org/wiki/Singleton_pattern#Python

Voici une variante qui va créer une instance différente en fonction des paramètres: (Il est pas parfait Si vous passez un paramètre qui est un dict, il le fera. échouer, par exemple.Mais c'est un début):

# Notice how you subclass not from object, but from type. You are in other words 
# creating a new type of type. 
class SingletonPerValue(type): 
    def __init__(cls, name, bases, dict): 
     super(SingletonPerValue, cls).__init__(name, bases, dict) 
     # Here we store the created instances later. 
     cls.instances = {} 

    def __call__(cls, *args, **kw): 
     # We make a tuple out of all parameters. This is so we can use it as a key 
     # This will fail if you send in unhasheable parameters. 
     params = args + tuple(kw.items()) 
     # Check in cls.instances if this combination of parameter has been used: 
     if params not in cls.instances: 
      # No, this is a new combination of parameters. Create a new instance, 
      # and store it in the dictionary: 
      cls.instances[params] = super(SingletonPerValue, cls).__call__(*args, **kw) 

     return cls.instances[params] 


class MyClass(object): 
    # Say that this class should use a specific metaclass: 
    __metaclass__ = SingletonPerValue 

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

print 1, MyClass(1) 
print 2, MyClass(2) 
print 2, MyClass(2) 
print 2, MyClass(2) 
print 3, MyClass(3) 

Mais il existe d'autres contraintes dans Python que l'instanciation. Beaucoup d'entre eux peuvent être fait avec des métaclasses. D'autres ont des raccourcis, voici une classe qui vous permet seulement de définir les attributs 'items' et 'fruit', par exemple.

class Constrained(object): 
    __slots__ = ['items', 'fruit'] 

con = Constrained() 
con.items = 6 
con.fruit = "Banana" 
con.yummy = True 

Si vous voulez des restrictions sur les attributs, mais pas tout à fait ce fort, vous pouvez remplacer __getattr__, __setattr__ and __delattr__ faire beaucoup de choses fantastiques et horribles se produisent. :-) Il y a aussi des paquetages qui vous permettent de définir des contraintes sur les attributs, etc.

+0

Votre réponse est très intéressante. J'ai tout de suite compris la réponse de TokenMacGuy, car elle est plus simple et commentée, mais votre introduction des métaclasses ouvre des possibilités encore plus grandes. Je suis juste en train d'essayer de comprendre - pouvez-vous mettre quelques commentaires dans le code que je lis sur les métaclasses pour la première fois! – Marco

+0

OK, c'est fait. Notez que la syntaxe pour utiliser les métaclasses a changé dans Python 3 ... –

1

Note: ce n'est pas vraiment une réponse à votre question, mais plutôt un commentaire que je ne pouvais pas insérer dans l'espace "commentaire".

Veuillez noter que a == b ne PAS implique a is b.

Il est vrai que pour les premières poignées d'entiers (comme la première centaine - je ne sais pas exactement) et ce n'est qu'un détail d'implémentation de CPython, qui a changé avec le passage à Python 3.0.

Par exemple:

>>> n1 = 4000 
>>> n2 = 4000 
>>> n1 == n2 
True 
>>> n1 is n2 
False 
+0

entiers "est" le même entre -6 et 256, au moins dans 2.5.1 – Markus

+0

@Markus qui est un effet d'optimisation/secondaire de l'implémentation de CPython, et ne fait pas partie de la définition de la langue. Donc, si vous deviez passer à une implémentation différente, vous ne pouvez pas assumer ce comportement. –

+0

c'est très dérangeant! cela signifie que la classe entière est mathématiquement non valide en tant que catégorie 0! – Marco

Questions connexes