Recherche…


Introduction

Python s'offre non seulement comme un langage de script populaire, mais prend également en charge le paradigme de programmation orientée objet. Les classes décrivent des données et fournissent des méthodes pour manipuler ces données, toutes regroupées sous un seul objet. De plus, les classes permettent l'abstraction en séparant les détails concrets de la mise en œuvre des représentations abstraites des données.

Les classes utilisant le code sont généralement plus faciles à lire, à comprendre et à gérer.

Héritage de base

L'héritage dans Python est basé sur des idées similaires utilisées dans d'autres langages orientés objet tels que Java, C ++, etc. Une nouvelle classe peut être dérivée d'une classe existante comme suit.

class BaseClass(object):
    pass

class DerivedClass(BaseClass):
    pass

La BaseClass est la classe ( parent ) existante et la classe DerivedClass est la nouvelle classe ( enfant ) qui hérite (ou sous - classe ) les attributs de BaseClass . Remarque : à partir de Python 2.2, toutes les classes héritent implicitement de la classe d' object , qui est la classe de base de tous les types intégrés.

Nous définissons une classe Rectangle parent dans l'exemple ci-dessous, qui hérite implicitement de l' object :

class Rectangle():
    def __init__(self, w, h):
        self.w = w
        self.h = h
    
    def area(self):
        return self.w * self.h
        
    def perimeter(self):
        return 2 * (self.w + self.h)

La classe Rectangle peut être utilisée comme classe de base pour définir une classe Square , car un carré est un cas particulier de rectangle.

class Square(Rectangle):
    def __init__(self, s):
        # call parent constructor, w and h are both s
        super(Square, self).__init__(s, s)
        self.s = s

La classe Square héritera automatiquement de tous les attributs de la classe Rectangle ainsi que de la classe d'objet. super() est utilisé pour appeler la __init__() de la classe Rectangle , appelant essentiellement toute méthode surchargée de la classe de base. Note : dans Python 3, super() ne nécessite aucun argument.

Les objets de classe dérivés peuvent accéder et modifier les attributs de ses classes de base:

r.area()
# Output: 12
r.perimeter()  
# Output: 14

s.area()
# Output: 4
s.perimeter()
# Output: 8

Fonctions intégrées qui fonctionnent avec l'héritage

issubclass(DerivedClass, BaseClass) : renvoie True si DerivedClass est une sous-classe de BaseClass

isinstance(s, Class) : renvoie True si s est une instance de Class ou de l'une des classes dérivées de Class

# subclass check        
issubclass(Square, Rectangle)
# Output: True

# instantiate
r = Rectangle(3, 4)
s = Square(2)

isinstance(r, Rectangle)  
# Output: True
isinstance(r, Square)
# Output: False
# A rectangle is not a square

isinstance(s, Rectangle)
# Output: True
# A square is a rectangle
isinstance(s, Square)
# Output: True

Variables de classe et d'instance

Les variables d'instance sont uniques pour chaque instance, tandis que les variables de classe sont partagées par toutes les instances.

class C:
    x = 2  # class variable

    def __init__(self, y):
        self.y = y  # instance variable

C.x
# 2
C.y
# AttributeError: type object 'C' has no attribute 'y'

c1 = C(3)
c1.x
# 2
c1.y
# 3

c2 = C(4)
c2.x
# 2
c2.y
# 4

Les variables de classe sont accessibles sur les instances de cette classe, mais l'affectation à l'attribut class créera une variable d'instance qui masque la variable de classe

c2.x = 4
c2.x
# 4
C.x
# 2

Notez que la mutation de variables de classe à partir d'instances peut entraîner des conséquences inattendues.

class D:
    x = []
    def __init__(self, item):
        self.x.append(item)  # note that this is not an assigment!

d1 = D(1)
d2 = D(2)

d1.x
# [1, 2]
d2.x
# [1, 2]
D.x
# [1, 2]

Méthodes liées, non liées et statiques

L'idée des méthodes liées et non liées a été supprimée dans Python 3 . Dans Python 3, lorsque vous déclarez une méthode dans une classe, vous utilisez un mot-clé def , créant ainsi un objet fonction. Ceci est une fonction régulière et la classe environnante fonctionne comme son espace de noms. Dans l'exemple suivant, nous déclarons la méthode f dans la classe A , qui devient une fonction Af :

Python 3.x 3.0
class A(object):
    def f(self, x):
        return 2 * x
A.f
# <function A.f at ...>  (in Python 3.x)

Dans Python 2, le comportement était différent: les objets fonction de la classe étaient implicitement remplacés par des objets de type instancemethod , appelés méthodes non liées, car ils n'étaient liés à aucune instance de classe particulière. Il était possible d'accéder à la fonction sous-jacente en utilisant la propriété .__func__ .

Python 2.x 2.3
A.f
# <unbound method A.f>   (in Python 2.x)
A.f.__class__
# <type 'instancemethod'>
A.f.__func__
# <function f at ...>

Ces derniers comportements sont confirmés par inspection - les méthodes sont reconnues comme des fonctions dans Python 3, tandis que la distinction est confirmée dans Python 2.

Python 3.x 3.0
import inspect

inspect.isfunction(A.f)
# True
inspect.ismethod(A.f)
# False
Python 2.x 2.3
import inspect

inspect.isfunction(A.f)
# False
inspect.ismethod(A.f)
# True

Dans les deux versions de Python, la fonction / méthode Af peut être appelée directement, à condition que vous passiez une instance de classe A en premier argument.

A.f(1, 7)
# Python 2: TypeError: unbound method f() must be called with
#                      A instance as first argument (got int instance instead) 
# Python 3: 14   
a = A()
A.f(a, 20)
# Python 2 & 3: 40

Maintenant, supposons a est une instance de la classe A , qu'est-ce que af alors? Eh bien, intuitivement, cela devrait être la même méthode f de la classe A , mais elle devrait en quelque sorte "savoir" qu'elle a été appliquée à l'objet a - en Python, cela s'appelle la méthode liée à a .

Les détails de la débrouille sont les suivantes: l' écriture af invoque la magie __getattribute__ méthode d' a , qui vérifie d' abord si a a un attribut nommé f (il n'a pas), puis vérifie la classe A si elle contient une méthode avec un tel nom (il le fait), et crée un nouvel objet m de type method qui a la référence à l' Af origine dans m.__func__ , et une référence à l'objet a in m.__self__ . Lorsque cet objet est appelé en tant que fonction, il fait simplement ce qui suit: m(...) => m.__func__(m.__self__, ...) . Ainsi, cet objet est appelé méthode liée car, lorsqu'il est appelé, il sait fournir l'objet auquel il était lié en tant que premier argument. (Ces choses fonctionnent de la même manière dans Python 2 et 3).

a = A()
a.f
# <bound method A.f of <__main__.A object at ...>>
a.f(2)
# 4

# Note: the bound method object a.f is recreated *every time* you call it:
a.f is a.f  # False
# As a performance optimization you can store the bound method in the object's
# __dict__, in which case the method object will remain fixed:
a.f = a.f
a.f is a.f  # True

Enfin, Python a des méthodes de classe et des méthodes statiques - des types spéciaux de méthodes. Les méthodes de classe fonctionnent de la même manière que les méthodes classiques, sauf que lorsqu'elles sont appelées sur un objet, elles sont liées à la classe de l'objet plutôt qu'à l'objet. Donc m.__self__ = type(a) . Lorsque vous appelez une telle méthode liée, elle passe la classe d' a comme premier argument. Les méthodes statiques sont encore plus simples: elles ne lient rien du tout et renvoient simplement la fonction sous-jacente sans aucune transformation.

class D(object):
    multiplier = 2

    @classmethod
    def f(cls, x):
        return cls.multiplier * x

    @staticmethod
    def g(name):
        print("Hello, %s" % name)

D.f
# <bound method type.f of <class '__main__.D'>>
D.f(12)
# 24
D.g
# <function D.g at ...>
D.g("world")
# Hello, world

Notez que les méthodes de classe sont liées à la classe même si elles sont accessibles sur l'instance:

d = D()
d.multiplier = 1337
(D.multiplier, d.multiplier)
# (2, 1337)
d.f
# <bound method D.f of <class '__main__.D'>>
d.f(10)
# 20

Il est à noter qu'au niveau le plus bas, les fonctions, méthodes, méthodes statiques, etc. sont en réalité des descripteurs qui invoquent les méthodes spéciales __get__ , __set __ et éventuellement __del__ . Pour plus de détails sur les méthodes de classe et les méthodes statiques:

Nouveau style vs classes anciennes

Python 2.x 2.2.0

Les classes de nouveau style ont été introduites dans Python 2.2 pour unifier les classes et les types . Ils héritent du type d' object niveau supérieur. Une classe de nouveau style est un type défini par l'utilisateur et très similaire aux types intégrés.

# new-style class
class New(object):
    pass

# new-style instance
new = New()

new.__class__
# <class '__main__.New'>
type(new)
# <class '__main__.New'>
issubclass(New, object)
# True

Les anciennes classes n'héritent pas de l' object . Les anciennes instances sont toujours implémentées avec un type d' instance intégré.

# old-style class
class Old:
    pass

# old-style instance
old = Old()

old.__class__
# <class __main__.Old at ...>
type(old)
# <type 'instance'>
issubclass(Old, object)
# False
Python 3.x 3.0.0

En Python 3, les anciennes classes ont été supprimées.

Les classes de nouveau style dans Python 3 héritent implicitement de l' object , il n'est donc plus nécessaire de spécifier MyClass(object) .

class MyClass:
    pass

my_inst = MyClass()

type(my_inst)
# <class '__main__.MyClass'>
my_inst.__class__
# <class '__main__.MyClass'>
issubclass(MyClass, object)
# True

Valeurs par défaut pour les variables d'instance

Si la variable contient une valeur d'un type immuable (par exemple une chaîne), il est possible d'attribuer une valeur par défaut comme celle-ci

class Rectangle(object):
    def __init__(self, width, height, color='blue'):
        self.width = width
        self.height = height
        self.color = color
    
    def area(self):
        return self.width  * self.height 

# Create some instances of the class
default_rectangle = Rectangle(2, 3)
print(default_rectangle.color) # blue

red_rectangle = Rectangle(2, 3, 'red')
print(red_rectangle.color) # red

Il faut faire attention lors de l'initialisation d'objets mutables tels que les listes dans le constructeur. Prenons l'exemple suivant:

class Rectangle2D(object):
    def __init__(self, width, height, pos=[0,0], color='blue'):  
        self.width = width
        self.height = height
        self.pos = pos
        self.color = color

r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [4, 0] r2's pos has changed as well

Ce comportement est dû au fait que dans Python, les paramètres par défaut sont liés à l'exécution de la fonction et non à la déclaration de la fonction. Pour obtenir une variable d’instance par défaut qui n’est pas partagée entre les instances, il faut utiliser une construction comme celle-ci:

class Rectangle2D(object):
    def __init__(self, width, height, pos=None, color='blue'):  
        self.width = width
        self.height = height
        self.pos = pos or [0, 0] # default value is [0, 0]
        self.color = color

r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [0, 0] r2's pos hasn't changed 

Voir également les arguments de Mutable par défaut et la «moindre surprise» et l'argument par défaut Mutable .

Héritage multiple

Python utilise l'algorithme de linéarisation C3 pour déterminer l'ordre de résolution des attributs de classe, y compris les méthodes. Ceci est connu comme l'Ordre de Résolution de Méthode (MRO).

Voici un exemple simple:

class Foo(object):
    foo = 'attr foo of Foo'
    

class Bar(object):
    foo = 'attr foo of Bar' # we won't see this.
    bar = 'attr bar of Bar'

class FooBar(Foo, Bar):
    foobar = 'attr foobar of FooBar'

Maintenant, si nous instancions FooBar, si nous recherchons l'attribut foo, nous voyons que l'attribut de Foo est trouvé en premier

fb = FooBar()

et

>>> fb.foo
'attr foo of Foo'

Voici le MRO de FooBar:

>>> FooBar.mro()
[<class '__main__.FooBar'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <type 'object'>]

On peut simplement dire que l’algorithme MRO de Python est

  1. Profondeur d'abord (par exemple FooBar puis Foo ) à moins que
  2. un parent partagé ( object ) est bloqué par un enfant ( Bar ) et
  3. aucune relation circulaire autorisée.

C'est-à-dire que, par exemple, Bar ne peut pas hériter de FooBar alors que FooBar hérite de Bar.

Pour un exemple complet en Python, voir l' entrée Wikipedia .

Une autre fonctionnalité puissante dans l'héritage est super . super peut récupérer les fonctionnalités des classes parentes.

class Foo(object):
    def foo_method(self):
        print "foo Method"

class Bar(object):
    def bar_method(self):
        print "bar Method"

class FooBar(Foo, Bar):
    def foo_method(self):
        super(FooBar, self).foo_method()

Héritage multiple avec la méthode init de la classe, lorsque chaque classe a sa propre méthode d'initialisation, alors nous essayons d'inférencer plusieurs fois, alors seule la méthode init est appelée de classe qui est héritée en premier.

pour exemple ci - dessous seule méthode init Foo classe se classe appelé Bar INIT pas appelé

    class Foo(object):
        def __init__(self):
            print "foo init"

    class Bar(object):
        def __init__(self):
            print "bar init"

    class FooBar(Foo, Bar):
        def __init__(self):
            print "foobar init"
            super(FooBar, self).__init__()

    a = FooBar()

Sortie:

    foobar init
    foo init

Mais cela ne signifie pas que la classe Bar n'est pas héritée. L'instance de la classe FooBar finale est également une instance de la classe Bar et de la classe Foo .

print isinstance(a,FooBar)
print isinstance(a,Foo)
print isinstance(a,Bar) 

Sortie:

True
True
True

Descripteurs et recherches par points

Les descripteurs sont des objets qui sont (généralement) des attributs de classes et qui ont des méthodes spéciales __get__ , __set__ ou __delete__ .

Les descripteurs de données ont l'un des __set__ ou __delete__

Ceux-ci peuvent contrôler la recherche pointillée sur une instance et sont utilisés pour implémenter des fonctions, staticmethod , classmethod et property . Une recherche pointillée (par exemple, instance foo de la classe Foo recherchant la bar attributs - c.-à-d. foo.bar ) utilise l'algorithme suivant:

  1. bar est levé dans la classe, Foo . S'il existe et qu'il s'agit d'un descripteur de données , le descripteur de données est utilisé. C'est comme cela que la property peut contrôler l'accès aux données dans une instance et que les instances ne peuvent pas remplacer cela. Si un descripteur de données n’est pas là, alors

  2. bar est recherché dans l'instance __dict__ . C'est pourquoi nous pouvons remplacer ou bloquer les méthodes appelées à partir d'une instance avec une recherche pointillée. Si la bar existe dans l'instance, elle est utilisée. Sinon, nous avons alors

  3. regardez dans la classe Foo for bar . S'il s'agit d'un descripteur , le protocole de descripteur est utilisé. Voici comment les fonctions (dans ce contexte, les méthodes non liées), classmethod et staticmethod sont implémentées. Sinon, il retourne simplement l'objet, ou il y a un AttributeError

Méthodes de classe: initialiseurs alternatifs

Les méthodes de classe présentent des méthodes alternatives pour générer des instances de classes. Pour illustrer, regardons un exemple.

Supposons que nous ayons une classe de Person relativement simple:

class Person(object):

    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age
        self.full_name = first_name + " " + last_name
    
    def greet(self):
        print("Hello, my name is " + self.full_name + ".")

Il pourrait être utile d'avoir un moyen de construire des instances de cette classe en spécifiant un nom complet au lieu du nom et du prénom séparément. Une façon de le faire serait de faire en sorte que last_name soit un paramètre facultatif, et en supposant que s’il n’est pas donné, nous avons passé le nom complet dans:

class Person(object):

    def __init__(self, first_name, age, last_name=None):
        if last_name is None:
            self.first_name, self.last_name = first_name.split(" ", 2)
        else:
            self.first_name = first_name
            self.last_name = last_name
        
        self.full_name = self.first_name + " " + self.last_name
        self.age = age

    def greet(self):
        print("Hello, my name is " + self.full_name + ".")

Cependant, il y a deux problèmes principaux avec ce bit de code:

  1. Les paramètres first_name et last_name sont maintenant trompeuses, puisque vous pouvez entrer un nom complet pour first_name . En outre, s'il y a plus de cas et / ou de paramètres ayant ce type de flexibilité, le branchement if / elif / else peut devenir rapidement ennuyeux.

  2. Pas tout à fait aussi important, mais ça vaut quand même la peine de le souligner: et si last_name est None , mais first_name ne se divise pas en deux ou plus via des espaces? Nous avons encore une autre couche de validation des entrées et / ou de gestion des exceptions ...

Entrez les méthodes de classe. Plutôt que d'avoir un seul initialiseur, nous allons créer un initialiseur distinct, appelé from_full_name , et le décorer avec le décorateur de classmethod (intégré).

class Person(object):

    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age
        self.full_name = first_name + " " + last_name
    
    @classmethod
    def from_full_name(cls, name, age):
        if " " not in name:
            raise ValueError
        first_name, last_name = name.split(" ", 2)
        return cls(first_name, last_name, age)
    
    def greet(self):
        print("Hello, my name is " + self.full_name + ".")

Remarquez cls au lieu de self comme premier argument de from_full_name . Les méthodes de classe sont appliquées à la classe globale, et non à une instance d'une classe donnée (ce que self désigne généralement). Donc, si cls est notre Person de classe, la valeur renvoyée par la from_full_name méthode de classe est Person(first_name, last_name, age) , qui utilise la Person de __init__ pour créer une instance de la Person classe. En particulier, si nous devions créer une sous-classe Employee of Person , alors from_full_name fonctionnerait également dans la classe Employee .

Pour montrer que cela fonctionne comme prévu, créons des instances de Person de plusieurs manières sans les branchements dans __init__ :

In [2]: bob = Person("Bob", "Bobberson", 42)

In [3]: alice = Person.from_full_name("Alice Henderson", 31)

In [4]: bob.greet()
Hello, my name is Bob Bobberson.

In [5]: alice.greet()
Hello, my name is Alice Henderson.

Autres références:

Composition de classe

La composition de classe permet des relations explicites entre les objets. Dans cet exemple, les gens vivent dans des villes appartenant à des pays. La composition permet aux personnes d'accéder au nombre de personnes vivant dans leur pays:

class Country(object):
    def __init__(self):
        self.cities=[]
        
    def addCity(self,city):
        self.cities.append(city)
        

class City(object):
    def __init__(self, numPeople):
        self.people = []
        self.numPeople = numPeople
        
        
    def addPerson(self, person):
        self.people.append(person)
    
    def join_country(self,country):
        self.country = country
        country.addCity(self)
        
        for i in range(self.numPeople):
                person(i).join_city(self)
  

class Person(object):
    def __init__(self, ID):
        self.ID=ID

    def join_city(self, city):
        self.city = city
        city.addPerson(self)
        
    def people_in_my_country(self):
        x= sum([len(c.people) for c in self.city.country.cities])
        return x
        
US=Country()
NYC=City(10).join_country(US)
SF=City(5).join_country(US)

print(US.cities[0].people[0].people_in_my_country())

# 15

Singe Patching

Dans ce cas, "patching singe" signifie l'ajout d'une nouvelle variable ou méthode à une classe après sa définition. Par exemple, disons que nous avons défini la classe A comme

class A(object):
    def __init__(self, num):
        self.num = num

    def __add__(self, other):
        return A(self.num + other.num)

Mais maintenant, nous voulons ajouter une autre fonction plus tard dans le code. Supposons que cette fonction est la suivante.

def get_num(self):
    return self.num

Mais comment ajouter cela comme méthode dans A ? C'est simple, nous plaçons essentiellement cette fonction dans A avec une déclaration d'affectation.

A.get_num = get_num

Pourquoi ça marche? Parce que les fonctions sont des objets comme n'importe quel autre objet, et que les méthodes sont des fonctions appartenant à la classe.

La fonction get_num doit être disponible pour tous les existants (déjà créés) ainsi que pour les nouvelles instances de A

Ces ajouts sont disponibles automatiquement sur toutes les instances de cette classe (ou de ses sous-classes). Par exemple:

foo = A(42)

A.get_num = get_num

bar = A(6);

foo.get_num() # 42

bar.get_num() # 6

Notez que, contrairement à d'autres langages, cette technique ne fonctionne pas pour certains types intégrés et n'est pas considérée comme un bon style.

Liste de tous les membres de la classe

La fonction dir() peut être utilisée pour obtenir une liste des membres d'une classe:

dir(Class)

Par exemple:

>>> dir(list)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Il est courant de ne chercher que des membres "non magiques". Cela peut être fait en utilisant une compréhension simple qui répertorie les membres dont le nom ne commence pas par __ :

>>> [m for m in dir(list) if not m.startswith('__')]
['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Mises en garde:

Les classes peuvent définir une __dir__() . Si cette méthode existe, appeler dir() appellera __dir__() , sinon Python essaiera de créer une liste des membres de la classe. Cela signifie que la fonction dir peut avoir des résultats inattendus. Deux citations importantes de la documentation officielle de python :

Si l'objet ne fournit pas dir (), la fonction essaie de son mieux de collecter des informations à partir de l'attribut dict de l'objet, s'il est défini, et de son objet de type. La liste résultante n'est pas nécessairement complète et peut être inexacte lorsque l'objet a un getattr () personnalisé.

Remarque: Étant donné que rép () est principalement utilisé pour une utilisation sur une invite interactive, il essaie de fournir un ensemble de noms intéressant plus qu’il ne tente de fournir un ensemble de noms rigoureusement ou systématiquement défini, et son comportement détaillé peut varier rejets. Par exemple, les attributs de métaclasse ne sont pas dans la liste de résultats lorsque l'argument est une classe.

Introduction aux cours

Une classe fonctionne comme un modèle définissant les caractéristiques de base d'un objet particulier. Voici un exemple:

class Person(object):
     """A simple class."""                            # docstring
     species = "Homo Sapiens"                         # class attribute

     def __init__(self, name):                        # special method
         """This is the initializer. It's a special
         method (see below).
         """
         self.name = name                             # instance attribute

     def __str__(self):                               # special method
         """This method is run when Python tries 
         to cast the object to a string. Return 
         this string when using print(), etc.
         """
         return self.name

     def rename(self, renamed):                       # regular method
         """Reassign and print the name attribute."""
         self.name = renamed
         print("Now my name is {}".format(self.name))

Il y a quelques points à noter lorsque vous regardez l'exemple ci-dessus.

  1. La classe est composée d' attributs (données) et de méthodes (fonctions).
  2. Les attributs et les méthodes sont simplement définis comme des variables et des fonctions normales.
  3. Comme indiqué dans le docstring correspondant, la __init__() s'appelle l' initialiseur . C'est l'équivalent du constructeur dans d'autres langages orientés objet, et c'est la méthode qui est exécutée en premier lorsque vous créez un nouvel objet ou une nouvelle instance de la classe.
  4. Les attributs qui s'appliquent à la classe entière sont définis en premier et sont appelés attributs de classe .
  5. Les attributs qui s'appliquent à une instance spécifique d'une classe (un objet) sont appelés attributs d'instance . Ils sont généralement définis dans __init__() ; ce n'est pas nécessaire, mais cela est recommandé (puisque les attributs définis en dehors de __init__() risquent d'être consultés avant d'être définis).
  6. Chaque méthode, incluse dans la définition de classe, passe l'objet en question comme premier paramètre. Le mot self est utilisé pour ce paramètre (l'utilisation de self est en réalité par convention, car le mot self n'a aucune signification inhérente en Python, mais c'est l'une des conventions les plus respectées de Python, et vous devriez toujours la suivre).
  7. Ceux qui sont habitués à la programmation orientée objet dans d'autres langues peuvent être surpris par certaines choses. L'une d'elles est que Python n'a pas de véritable concept d'éléments private , donc tout, par défaut, imite le comportement du mot clé public C ++ / Java. Pour plus d'informations, reportez-vous à l'exemple "Private Class Members" sur cette page.
  8. Certaines des méthodes de la classe ont la forme suivante: __functionname__(self, other_stuff) . Toutes ces méthodes sont appelées "méthodes magiques" et constituent une partie importante des classes en Python. Par exemple, la surcharge de l'opérateur en Python est implémentée avec des méthodes magiques. Pour plus d'informations, consultez la documentation appropriée .

Faisons maintenant quelques exemples de notre classe Person !

>>> # Instances
>>> kelly = Person("Kelly")
>>> joseph = Person("Joseph")
>>> john_doe = Person("John Doe")

Nous avons actuellement trois objets Person , kelly , joseph et john_doe .

Nous pouvons accéder aux attributs de la classe à partir de chaque instance à l'aide de l'opérateur point . Notez à nouveau la différence entre les attributs de classe et d'instance:

>>> # Attributes
>>> kelly.species
'Homo Sapiens'
>>> john_doe.species
'Homo Sapiens'
>>> joseph.species
'Homo Sapiens'
>>> kelly.name
'Kelly'
>>> joseph.name
'Joseph'

Nous pouvons exécuter les méthodes de la classe en utilisant le même opérateur de points . :

>>> # Methods
>>> john_doe.__str__()
'John Doe'
>>>  print(john_doe)
'John Doe'
>>>  john_doe.rename("John")
'Now my name is John'

Propriétés

Les classes Python prennent en charge les propriétés , qui ressemblent à des variables d'objet classiques, mais avec la possibilité d'attacher un comportement et une documentation personnalisés.

class MyClass(object):

    def __init__(self):
       self._my_string = ""
    
    @property
    def string(self):
        """A profoundly important string."""
        return self._my_string

    @string.setter
    def string(self, new_value):
        assert isinstance(new_value, str), \
               "Give me a string, not a %r!" % type(new_value)
        self._my_string = new_value

    @string.deleter
    def x(self):
        self._my_string = None

L'objet de la classe MyClass semblera avoir une propriété .string , mais son comportement est maintenant étroitement contrôlé:

mc = MyClass()
mc.string = "String!"
print(mc.string)
del mc.string

Outre la syntaxe utile ci-dessus, la syntaxe de la propriété permet la validation ou d’autres augmentations à ajouter à ces attributs. Cela pourrait être particulièrement utile avec les API publiques - où un niveau d'aide devrait être donné à l'utilisateur.

Une autre utilisation courante des propriétés est de permettre à la classe de présenter des «attributs virtuels» - des attributs qui ne sont pas réellement stockés mais qui ne sont calculés que sur demande.

class Character(object):
    def __init__(name, max_hp):
        self._name = name
        self._hp = max_hp
        self._max_hp = max_hp

    # Make hp read only by not providing a set method
    @property
    def hp(self):
        return self._hp

    # Make name read only by not providing a set method
    @property
    def name(self):
        return self.name

    def take_damage(self, damage):
        self.hp -= damage
        self.hp = 0 if self.hp <0 else self.hp

    @property
    def is_alive(self):
        return self.hp != 0

    @property
    def is_wounded(self):
        return self.hp < self.max_hp if self.hp > 0 else False

    @property
    def is_dead(self):
        return not self.is_alive

bilbo = Character('Bilbo Baggins', 100)
bilbo.hp
# out : 100
bilbo.hp = 200        
# out : AttributeError: can't set attribute
# hp attribute is read only.

bilbo.is_alive
# out : True
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : False

bilbo.take_damage( 50 )

bilbo.hp
# out : 50

bilbo.is_alive
# out : True
bilbo.is_wounded
# out : True
bilbo.is_dead
# out : False

bilbo.take_damage( 50 )
bilbo.hp
# out : 0

bilbo.is_alive
# out : False
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : True

Classe Singleton

Un singleton est un modèle qui limite l'instanciation d'une classe à une instance / objet. Pour plus d'informations sur les modèles de conception de singleton python, voir ici .

class Singleton:
    def __new__(cls):
        try:
            it = cls.__it__
        except AttributeError:
            it = cls.__it__ = object.__new__(cls)
        return it

    def __repr__(self):
        return '<{}>'.format(self.__class__.__name__.upper())

    def __eq__(self, other):
         return other is self

Une autre méthode consiste à décorer votre classe. En suivant l'exemple de cette réponse, créez une classe Singleton:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

Pour utiliser, vous pouvez utiliser la méthode Instance

@Singleton
class Single:
    def __init__(self):
        self.name=None
        self.val=0
    def getName(self):
        print(self.name)

x=Single.Instance()
y=Single.Instance()
x.name='I\'m single'
x.getName() # outputs I'm single
y.getName() # outputs I'm single


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow