GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing docs.python.org — 9. Classes 9. Classes¶Les classes sont un moyen de réunir des données et des fonctionnalités. Créer une nouvelle classe crée un nouveau type d'objet et ainsi de nouvelles instances de ce type peuvent être construites. Chaque instance peut avoir ses propres attributs, ce qui définit son état. Une instance peut aussi avoir des méthodes (définies par la classe de l'instance) pour modifier son état. La notion de classes en Python s'inscrit dans le langage avec un minimum de syntaxe et de sémantique nouvelles. C'est un mélange des mécanismes rencontrés dans C++ et Modula-3. Les classes fournissent toutes les fonctionnalités standards de la programmation orientée objet : l'héritage de classes autorise les héritages multiples, une classe dérivée peut surcharger les méthodes de sa ou ses classes mères et une méthode peut appeler la méthode d'une classe mère qui possède le même nom. Les objets peuvent contenir n'importe quel nombre ou type de données. De la même manière que les modules, les classes participent à la nature dynamique de Python : elles sont créées pendant l'exécution et peuvent être modifiées après leur création. Dans la terminologie C++, les membres des classes (y compris les données) sont publics (sauf exception, voir Variables privées) et toutes les fonctions membres sont virtuelles. Comme avec Modula-3, il n'y a aucune façon d'accéder aux membres d'un objet à partir de ses méthodes : une méthode est déclarée avec un premier argument explicite représentant l'objet et cet argument est transmis de manière implicite lors de l'appel. Comme avec Smalltalk, les classes elles-mêmes sont des objets. Il existe ainsi une sémantique pour les importer et les renommer. Au contraire de C++ et Modula-3, les types natifs peuvent être utilisés comme classes mères pour être étendus par l'utilisateur. Enfin, comme en C++, la plupart des opérateurs natifs avec une syntaxe spéciale (opérateurs arithmétiques, indiçage, etc.) peuvent être redéfinis pour les instances de classes. En l'absence d'une terminologie communément admise pour parler des classes, nous utilisons parfois des termes de Smalltalk et C++. Nous voulions utiliser les termes de Modula-3 puisque sa sémantique orientée objet est plus proche de celle de Python que C++, mais il est probable que seul un petit nombre de lecteurs les connaissent. 9.1. Objets et noms : préambule¶Les objets possèdent une existence propre et plusieurs noms peuvent être utilisés (dans divers contextes) pour faire référence à un même objet. Ce concept est connu sous le nom d'alias dans d'autres langages. Il n'apparaît pas au premier coup d'œil en Python et il peut être ignoré tant qu'on travaille avec des types de base immuables (nombres, chaînes, n-uplets). Cependant, les alias peuvent produire des effets surprenants sur la sémantique d'un code Python mettant en jeu des objets mutables comme les listes, les dictionnaires et la plupart des autres types. En général, leur utilisation est bénéfique au programme car les alias se comportent, d'un certain point de vue, comme des pointeurs. Par exemple, transmettre un objet n'a aucun coût car c'est simplement un pointeur qui est transmis par l'implémentation ; et si une fonction modifie un objet passé en argument, le code à l'origine de l'appel voit le changement. Ceci élimine le besoin d'avoir deux mécanismes de transmission d'arguments comme en Pascal. 9.2. Portées et espaces de nommage en Python¶Avant de présenter les classes, nous devons aborder la notion de portée en Python. Les définitions de classes font d'habiles manipulations avec les espaces de nommage, vous devez donc savoir comment les portées et les espaces de nommage fonctionnent. Soit dit en passant, la connaissance de ce sujet est aussi utile aux développeurs Python expérimentés. Commençons par quelques définitions. Un espace de nommage est une table de correspondance entre des noms et des objets. La plupart des espaces de nommage sont actuellement implémentés sous forme de dictionnaires Python, mais ceci n'est normalement pas visible (sauf pour les performances) et peut changer dans le futur. Comme exemples d'espaces de nommage, nous pouvons citer les primitives (fonctions comme À ce propos, nous utilisons le mot attribut pour tout nom suivant un point. Par exemple, dans l'expression Les attributs peuvent être en lecture seule ou modifiables. S'ils sont modifiables, l'affectation à un attribut est possible. Les attributs de modules sont modifiables : vous pouvez écrire Les espaces de nommage sont créés à différents moments et ont différentes durées de vie. L'espace de nommage contenant les primitives est créé au démarrage de l'interpréteur Python et n'est jamais effacé. L'espace de nommage globaux pour un module est créé lorsque la définition du module est lue. Habituellement, les espaces de nommage des modules durent aussi jusqu'à l'arrêt de l'interpréteur. Les instructions exécutées par la première invocation de l'interpréteur, qu'elles soient lues depuis un fichier de script ou de manière interactive, sont considérées comme faisant partie d'un module appelé L'espace des noms locaux d'une fonction est créé lors de son appel, puis effacé lorsqu'elle renvoie un résultat ou lève une exception non prise en charge (en fait, « oublié » serait une meilleure façon de décrire ce qui se passe réellement). Bien sûr, des invocations récursives ont chacune leur propre espace de nommage. La portée est la zone textuelle d'un programme Python où un espace de nommage est directement accessible. « Directement accessible » signifie ici qu'une référence non qualifiée à un nom est cherchée dans l'espace de nommage. Bien que les portées soient déterminées de manière statique, elles sont utilisées de manière dynamique. À n'importe quel moment de l'exécution, il y a au minimum trois ou quatre portées imbriquées dont les espaces de nommage sont directement accessibles :
Si un nom est déclaré comme global, alors toutes les références et affectations vont directement dans l'avant-dernière portée contenant les noms globaux du module. Pour pointer une variable qui se trouve en dehors de la portée la plus locale, vous pouvez utiliser l'instruction Habituellement, la portée locale référence les noms locaux de la fonction courante. En dehors des fonctions, la portée locale référence le même espace de nommage que la portée globale : l'espace de nommage du module. Les définitions de classes créent un nouvel espace de nommage dans la portée locale. Il est important de réaliser que les portées sont déterminées de manière textuelle : la portée globale d'une fonction définie dans un module est l'espace de nommage de ce module, quelle que soit la provenance de l'appel à la fonction. En revanche, la recherche réelle des noms est faite dynamiquement au moment de l'exécution. Cependant la définition du langage est en train d'évoluer vers une résolution statique des noms au moment de la « compilation », donc ne vous basez pas sur une résolution dynamique (en réalité, les variables locales sont déjà déterminées de manière statique) ! Une particularité de Python est que, si aucune instruction L'instruction 9.2.1. Exemple de portées et d'espaces de nommage¶Ceci est un exemple montrant comment utiliser les différentes portées et espaces de nommage, et comment def scope_test(): def do_local(): spam = "local spam" def do_nonlocal(): nonlocal spam spam = "nonlocal spam" def do_global(): global spam spam = "global spam" spam = "test spam" do_local() print("After local assignment:", spam) do_nonlocal() print("After nonlocal assignment:", spam) do_global() print("After global assignment:", spam) scope_test() print("In global scope:", spam) Ce code donne le résultat suivant : After local assignment: test spam After nonlocal assignment: nonlocal spam After global assignment: nonlocal spam In global scope: global spam Vous pouvez constater que l'affectation locale (qui est effectuée par défaut) n'a pas modifié la liaison de spam dans scope_test. L'affectation Vous pouvez également voir qu'aucune liaison pour spam n'a été faite avant l'affectation 9.3. Une première approche des classes¶Le concept de classe introduit un peu de syntaxe nouvelle, trois nouveaux types d'objets ainsi que quelques nouveaux éléments de sémantique. 9.3.1. Syntaxe de définition des classes¶La forme la plus simple de définition d'une classe est la suivante : class ClassName: <statement-1> . . . <statement-N> Les définitions de classes, comme les définitions de fonctions (définitions Dans la pratique, les déclarations dans une définition de classe sont généralement des définitions de fonctions mais d'autres déclarations sont permises et parfois utiles (nous revenons sur ce point plus tard). Les définitions de fonction à l'intérieur d'une classe ont normalement une forme particulière de liste d'arguments, dictée par les conventions d'appel aux méthodes (à nouveau, tout ceci est expliqué plus loin). Quand une classe est définie, un nouvel espace de nommage est créé et utilisé comme portée locale. Ainsi, toutes les affectations de variables locales entrent dans ce nouvel espace de nommage. En particulier, les définitions de fonctions y lient le nom de la nouvelle fonction. À la fin de la définition d'une classe, un objet classe est créé. C'est, pour simplifier, une encapsulation du contenu de l'espace de nommage créé par la définition de classe. Nous en apprendrons plus sur les objets classes dans la prochaine section. La portée locale initiale (celle qui prévaut avant le début de la définition de la classe) est ré-instanciée et l'objet de classe est lié ici au nom de classe donné dans l'en-tête de définition de classe ( 9.3.2. Objets classes¶Les objets classes prennent en charge deux types d'opérations : des références à des attributs et l'instanciation. Les références d'attributs utilisent la syntaxe standard utilisée pour toutes les références d'attributs en Python : class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world' alors L'instanciation de classes utilise la notation des fonctions. Considérez simplement que l'objet classe est une fonction sans paramètre qui renvoie une nouvelle instance de la classe. Par exemple (en considérant la classe définie ci-dessus) : x = MyClass() crée une nouvelle instance de la classe et affecte cet objet à la variable locale L'opération d'instanciation (en "appelant" un objet classe) crée un objet vide. De nombreuses classes aiment créer des instances personnalisées correspondant à un état initial spécifique. À cet effet, une classe peut définir une méthode spéciale nommée def __init__(self): self.data = [] Quand une classe définit une méthode x = MyClass() Bien sûr, la méthode >>> class Complex: ... def __init__(self, realpart, imagpart): ... self.r = realpart ... self.i = imagpart ... >>> x = Complex(3.0, -4.5) >>> x.r, x.i (3.0, -4.5) 9.3.3. Objets instances¶Maintenant, que pouvons-nous faire avec des objets instances ? Les seules opérations comprises par les objets instances sont des références d'attributs. Il y a deux sortes de noms d'attributs valides, les attributs 'données' et les méthodes. Les attributs 'données' correspondent à des « variables d'instance » en Smalltalk et aux « membres de données » en C++. Les attributs 'données' n'ont pas à être déclarés. Comme les variables locales, ils existent dès lors qu'ils sont assignés une première fois. Par exemple, si x.counter = 1 while x.counter < 10: x.counter = x.counter * 2 print(x.counter) del x.counter La deuxième sorte d'attribut d'instance est une méthode. Une méthode est une fonction qui « appartient à » un objet. Les noms de méthodes valides d'un objet instance dépendent de sa classe. Par définition, tous les attributs d'une classe qui sont des objets fonctions définissent les méthodes correspondantes de ses instances. Donc, dans notre exemple, 9.3.4. Objets méthode¶Le plus souvent, une méthode est appelée juste après avoir été liée : x.f() Si xf = x.f while True: print(xf()) affiche Que se passe-t-il exactement quand une méthode est appelée ? Vous avez dû remarquer que En fait, vous avez peut-être deviné la réponse : la particularité des méthodes est que l'objet est passé comme premier argument de la fonction. Dans notre exemple, l'appel En général, les méthodes marchent de la manière suivante. Lorsque un attribut d'une instance est référencé et que ce n'est pas un attribut 'données', sa classe est recherchée. Si le nom correspond à un attribut valide et que c'est un objet fonction, des références à l'objet instance et l'objet fonction sont empaquetées dans un objet méthode. Quand l'objet méthode est appelé avec une liste d'arguments, une nouvelle liste d'arguments est construite à partir de l'objet instance et de la liste des arguments. L'objet fonction est alors appelé avec cette nouvelle liste d'arguments. 9.3.5. Variables de classe et d’instance¶En général, les variables d'instance stockent des informations relatives à chaque instance alors que les variables de classe servent à stocker les attributs et méthodes communes à toutes les instances de la classe : class Dog: kind = 'canine' # class variable shared by all instances def __init__(self, name): self.name = name # instance variable unique to each instance >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.kind # shared by all dogs 'canine' >>> e.kind # shared by all dogs 'canine' >>> d.name # unique to d 'Fido' >>> e.name # unique to e 'Buddy' As discussed in Objets et noms : préambule, shared data can have possibly surprising effects involving mutable objects such as lists and dictionaries. For example, the tricks list in the following code should not be used as a class variable because just a single list would be shared by all Dog instances: class Dog: tricks = [] # mistaken use of a class variable def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks # unexpectedly shared by all dogs ['roll over', 'play dead'] Une conception correcte de la classe est d'utiliser une variable d'instance à la place : class Dog: def __init__(self, name): self.name = name self.tricks = [] # creates a new empty list for each dog def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks ['roll over'] >>> e.tricks ['play dead'] 9.4. Remarques diverses¶Si le même nom d'attribut apparaît à la fois dans une instance et dans une classe, alors la recherche d'attribut donne la priorité à l'instance : >>> class Warehouse: ... purpose = 'storage' ... region = 'west' ... >>> w1 = Warehouse() >>> print(w1.purpose, w1.region) storage west >>> w2 = Warehouse() >>> w2.region = 'east' >>> print(w2.purpose, w2.region) storage east Les attributs 'données' peuvent être référencés par des méthodes comme par des utilisateurs ordinaires ("clients") d'un objet. En d'autres termes, les classes ne sont pas utilisables pour implémenter des types de données purement abstraits. En fait, il n'est pas possible en Python d'imposer de masquer des données — tout est basé sur des conventions (d'un autre côté, l'implémentation de Python, écrite en C, peut complètement masquer les détails d'implémentation et contrôler l'accès à un objet si nécessaire ; ceci peut être utilisé par des extensions de Python écrites en C). Les clients doivent utiliser les attributs 'données' avec précaution, ils pourraient mettre le désordre dans les invariants gérés par les méthodes avec leurs propres valeurs d'attributs. Remarquez que les clients peuvent ajouter leurs propres attributs 'données' à une instance d'objet sans altérer la validité des méthodes, pour autant que les noms n'entrent pas en conflit, là aussi adopter une convention de nommage peut éviter bien des problèmes. Il n'y a pas de notation abrégée pour référencer des attributs 'données' (ou les autres méthodes !) depuis les méthodes. Nous pensons que ceci améliore en fait la lisibilité des méthodes : il n'y a aucune chance de confondre variables locales et variables d'instances quand on regarde le code d'une méthode. Souvent, le premier argument d'une méthode est nommé Tout objet fonction qui est un attribut de classe définit une méthode pour des instances de cette classe. Il n'est pas nécessaire que le texte de définition de la fonction soit dans la définition de la classe : il est possible d'affecter un objet fonction à une variable locale de la classe. Par exemple : # Function defined outside the class def f1(self, x, y): return min(x, x+y) class C: f = f1 def g(self): return 'hello world' h = g Maintenant, Les méthodes peuvent appeler d'autres méthodes en utilisant des méthodes qui sont des attributs de l'argument class Bag: def __init__(self): self.data = [] def add(self, x): self.data.append(x) def addtwice(self, x): self.add(x) self.add(x) Les méthodes peuvent faire référence à des noms globaux de la même manière que les fonctions. La portée globale associée à une méthode est le module contenant la définition de la classe (la classe elle-même n'est jamais utilisée en tant que portée globale). Alors qu'il est rare d'avoir une bonne raison d'utiliser des données globales dans une méthode, il y a de nombreuses utilisations légitimes de la portée globale : par exemple, les fonctions et modules importés dans une portée globale peuvent être utilisés par des méthodes, de même que les fonctions et classes définies dans cette même portée. Habituellement, la classe contenant la méthode est elle-même définie dans cette portée globale et, dans la section suivante, nous verrons de bonnes raisons pour qu'une méthode référence sa propre classe. Toute valeur est un objet et a donc une classe (appelée aussi son type). Elle est stockée dans 9.5. Héritage¶Bien sûr, ce terme de "classe" ne serait pas utilisé s'il n'y avait pas d'héritage. La syntaxe pour définir une sous-classe est de cette forme : class DerivedClassName(BaseClassName): <statement-1> . . . <statement-N> Le nom class DerivedClassName(modname.BaseClassName): L'exécution d'une définition de classe dérivée se déroule comme pour une classe mère. Quand l'objet de la classe est construit, la classe mère est mémorisée. Elle est utilisée pour la résolution des références d'attributs : si un attribut n'est pas trouvé dans la classe, la recherche se poursuit en regardant dans la classe mère. Cette règle est appliquée récursivement si la classe mère est elle-même dérivée d'une autre classe. Il n'y a rien de particulier dans l'instanciation des classes dérivées : Les classes dérivées peuvent surcharger des méthodes de leurs classes mères. Comme les méthodes n'ont aucun privilège particulier quand elles appellent d'autres méthodes d'un même objet, une méthode d'une classe mère qui appelle une autre méthode définie dans la même classe peut en fait appeler une méthode d'une classe dérivée qui la surcharge (pour les programmeurs C++ : toutes les méthodes de Python sont en effet "virtuelles"). Une méthode dans une classe dérivée peut aussi, en fait, vouloir étendre plutôt que simplement remplacer la méthode du même nom de sa classe mère. L'appel direct à la méthode de la classe mère s'écrit simplement Python définit deux fonctions primitives pour gérer l'héritage :
9.5.1. Héritage multiple¶Python gère également une forme d'héritage multiple. Une définition de classe ayant plusieurs classes mères est de cette forme : class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N> Dans la plupart des cas, vous pouvez vous représenter la recherche d'attributs dans les classes parentes comme étant : le plus profond d'abord, de gauche à droite, sans chercher deux fois dans la même classe si elle apparaît plusieurs fois dans la hiérarchie. Ainsi, si un attribut n'est pas trouvé dans Dans les faits, c'est un peu plus complexe que ça ; l'ordre de la recherche (method resolution order, ou MRO en anglais) change dynamiquement pour gérer des appels coopératifs à L'ordre défini dynamiquement est nécessaire car tous les cas d'héritage multiple comportent une ou plusieurs relations en losange (où au moins une classe peut être accédée à partir de plusieurs chemins en partant de la classe la plus basse). Par exemple, puisque toutes les classes héritent de In some cases multiple inheritance is not allowed; see Multiple inheritance for details. 9.6. Variables privées¶Les membres "privés", qui ne peuvent être accédés que depuis l'intérieur d'un objet, n'existent pas en Python. Toutefois, il existe une convention respectée par la majorité du code Python : un nom préfixé par un tiret bas (comme Dès lors qu'il y a un cas d'utilisation valable pour avoir des attributs privés aux classes (notamment pour éviter des conflits avec des noms définis dans des sous-classes), il existe un support (certes limité) pour un tel mécanisme, appelé name mangling. Tout identifiant de la forme Voir aussi La spécification des transformations de noms pour les détails et cas particuliers. Ce changement de nom est utile pour permettre à des sous-classes de surcharger des méthodes sans casser les appels de méthodes à l'intérieur d'une classe. Par exemple : class Mapping: def __init__(self, iterable): self.items_list = [] self.__update(iterable) def update(self, iterable): for item in iterable: self.items_list.append(item) __update = update # private copy of original update() method class MappingSubclass(Mapping): def update(self, keys, values): # provides new signature for update() # but does not break __init__() for item in zip(keys, values): self.items_list.append(item) L'exemple si dessus fonctionnerait même si Notez que ces règles sont conçues avant tout pour éviter les accidents ; il reste possible d'accéder ou de modifier une variable considérée comme privée. Ceci peut même être utile dans certaines circonstances, comme au sein du débogueur. Remarquez que le code que vous passez à 9.7. Trucs et astuces¶Il est parfois utile d'avoir un type de donnée similaire au record du Pascal ou au struct du C, qui regroupent ensemble quelques attributs « données » nommés. L'approche idiomatique correspondante en Python est d'utiliser des from dataclasses import dataclass @dataclass class Employee: name: str dept: str salary: int >>> john = Employee('john', 'computer lab', 1000) >>> john.dept 'computer lab' >>> john.salary 1000 À du code Python qui s'attend à recevoir un type de donnée abstrait spécifique, on peut souvent fournir une classe qui simule les méthodes de ce type. Par exemple, à une fonction qui formate des données extraites d'un objet fichier, vous pouvez lui passer comme argument une instance d'une classe qui implémente les méthodes Les objets méthode d'instance ont aussi des attributs : 9.8. Itérateurs¶Vous avez maintenant certainement remarqué que l'on peut itérer sur la plupart des objets conteneurs en utilisant une instruction for element in [1, 2, 3]: print(element) for element in (1, 2, 3): print(element) for key in {'one':1, 'two':2}: print(key) for char in "123": print(char) for line in open("myfile.txt"): print(line, end='') Ce style est simple, concis et pratique. L'utilisation d'itérateurs imprègne et unifie Python. En arrière plan, l'instruction >>> s = 'abc' >>> it = iter(s) >>> it <str_iterator object at 0x10c90e650> >>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last): File "<stdin>", line 1, in <module> next(it) StopIteration Une fois compris les mécanismes de gestion des itérateurs, il est simple d'ajouter ce comportement à vos classes. Définissez une méthode class Reverse: """Iterator for looping over a sequence backwards.""" def __init__(self, data): self.data = data self.index = len(data) def __iter__(self): return self def __next__(self): if self.index == 0: raise StopIteration self.index = self.index - 1 return self.data[self.index] >>> rev = Reverse('spam') >>> iter(rev) <__main__.Reverse object at 0x00A1DB50> >>> for char in rev: ... print(char) ... m a p s 9.9. Générateurs¶Les générateurs sont des outils simples et puissants pour créer des itérateurs. Ils sont écrits comme des fonctions classiques mais utilisent l'instruction def reverse(data): for index in range(len(data)-1, -1, -1): yield data[index] >>> for char in reverse('golf'): ... print(char) ... f l o g Tout ce qui peut être fait avec des générateurs peut également être fait avec des itérateurs basés sur des classes, comme décrit dans le paragraphe précédent. Ce qui rend les générateurs si compacts, c'est que les méthodes Une autre fonctionnalité clé est que les variables locales ainsi que le contexte d'exécution sont sauvegardés automatiquement entre les appels. Cela simplifie d'autant plus l'écriture de ces fonctions et rend leur code beaucoup plus lisible qu'avec une approche utilisant des variables d'instance telles que En plus de la création automatique de méthodes et de la sauvegarde du contexte d'exécution, les générateurs lèvent automatiquement une exception 9.10. Expressions et générateurs¶Des générateurs simples peuvent être codés très rapidement avec des expressions utilisant la même syntaxe que les compréhensions de listes, mais en utilisant des parenthèses à la place des crochets. Ces expressions sont conçues pour des situations où le générateur est utilisé tout de suite dans une fonction. Ces expressions sont plus compactes mais moins souples que des définitions complètes de générateurs et ont tendance à être plus économes en mémoire que leur équivalent en compréhension de listes. Exemples : >>> sum(i*i for i in range(10)) # sum of squares 285 >>> xvec = [10, 20, 30] >>> yvec = [7, 5, 3] >>> sum(x*y for x,y in zip(xvec, yvec)) # dot product 260 >>> unique_words = set(word for line in page for word in line.split()) >>> valedictorian = max((student.gpa, student.name) for student in graduates) >>> data = 'golf' >>> list(data[i] for i in range(len(data)-1, -1, -1)) ['f', 'l', 'o', 'g'] >>> x = [[1,2,3], [], [4, 5]] >>> g = (*i for i in x) >>> list(g) [1, 2, 3, 4, 5] In most cases, generator expressions must be wrapped in parentheses. As a
special case, however, when provided as the sole argument to a function (as in
the examples involving >>> f(x for x in y) >>> f((x for x in y)) as are the following: >>> f(*x for x in y) >>> f((*x for x in y)) Notes de bas de page
© Copyright 2001 Python Software Foundation.
La licence de cette page est la Python Software Foundation License Version 2. La licence des exemples, recettes, et autres morceaux de code se trouvant dans la documentation est la Zero Clause BSD License. Pour plus d'informations, consultez Histoire et licence. La Python Software Foundation est une association à but non lucratif. Elle vit de vos dons. Dernière mise à jour : nov. 12, 2026 (05:04 UTC). Un bogue ? Crée par Sphinx 9.1.1. |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |