Ricerca…


introduzione

Python si offre non solo come un popolare linguaggio di scripting, ma supporta anche il paradigma di programmazione orientato agli oggetti. Le classi descrivono i dati e forniscono metodi per manipolare tali dati, tutti racchiusi in un singolo oggetto. Inoltre, le classi consentono l'astrazione separando i dettagli di implementazione concreti dalle rappresentazioni astratte dei dati.

Il codice che utilizza le classi è generalmente più facile da leggere, capire e mantenere.

Ereditarietà di base

L'ereditarietà di Python si basa su idee simili utilizzate in altri linguaggi object oriented come Java, C ++, ecc. Una nuova classe può essere derivata da una classe esistente come segue.

class BaseClass(object):
    pass

class DerivedClass(BaseClass):
    pass

Il BaseClass è il già esistente (genitore) di classe, e la DerivedClass è il nuovo (bambino) classe che eredita (o sottoclassi) attributi da BaseClass . Nota : A partire da Python 2.2, tutte le classi ereditano implicitamente dalla classe object , che è la classe base per tutti i tipi built-in.

Definiamo una classe Rectangle genitore nell'esempio seguente, che eredita implicitamente 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 può essere utilizzata come classe base per definire una classe Square , poiché un quadrato è un caso speciale di rettangolo.

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 erediterà automaticamente tutti gli attributi della classe Rectangle e della classe dell'oggetto. super() è usato per chiamare il metodo __init__() della classe Rectangle , essenzialmente chiamando qualsiasi metodo sovrascritto della classe base. Nota : in Python 3, super() non richiede argomenti.

Gli oggetti classe derivati ​​possono accedere e modificare gli attributi delle sue classi base:

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

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

Funzioni integrate che funzionano con l'ereditarietà

issubclass(DerivedClass, BaseClass) : restituisce True se DerivedClass è una sottoclasse di BaseClass

isinstance(s, Class) : restituisce True se s è un'istanza di Class o una delle classi derivate di 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

Classi e variabili di istanza

Le variabili di istanza sono univoche per ogni istanza, mentre le variabili di classe sono condivise da tutte le istanze.

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

È possibile accedere alle variabili di classe sulle istanze di questa classe, ma assegnando all'attributo class verrà creata una variabile di istanza che ombreggia la variabile di classe

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

Si noti che mutando variabili di classe dalle istanze può portare ad alcune conseguenze inaspettate.

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]

Metodi legati, non associati e statici

L'idea dei metodi bound e nonbound è stata rimossa in Python 3 . In Python 3 quando dichiari un metodo all'interno di una classe, stai usando una parola chiave def , creando così un oggetto funzione. Questa è una funzione regolare e la classe circostante funziona come il suo spazio dei nomi. Nell'esempio seguente dichiariamo il metodo f all'interno della classe A , e diventa una funzione 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)

In Python 2 il comportamento era diverso: gli oggetti funzione all'interno della classe venivano implicitamente sostituiti con oggetti di tipo instancemethod , che venivano chiamati metodi non associati perché non erano associati a nessuna particolare istanza di classe. Era possibile accedere alla funzione sottostante usando la proprietà .__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 ...>

Questi ultimi comportamenti sono confermati dall'ispezione: i metodi sono riconosciuti come funzioni in Python 3, mentre la distinzione è confermata in 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

In entrambe le versioni della funzione / metodo Python, Af può essere chiamato direttamente, a condizione di passare un'istanza di classe A come primo argomento.

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

Ora supponiamo a è un'istanza della classe A , che cosa è af allora? Beh, intuitivamente questo dovrebbe essere lo stesso metodo f di classe A , solo che dovrebbe in qualche modo "sapere" che è stato applicato all'oggetto a - in Python questo si chiama il metodo legato a a .

I dettagli essenziali sono i seguenti: la scrittura af invoca la magia __getattribute__ metodo a , che controlla innanzitutto se a ha un attributo denominato f (che non), quindi controlla la classe A se contiene un metodo con un tale nome (lo fa), e crea un nuovo oggetto m di tipo method che ha il riferimento al Af originale in m.__func__ , e un riferimento all'oggetto a in m.__self__ . Quando questo oggetto viene chiamato come funzione, fa semplicemente quanto segue: m(...) => m.__func__(m.__self__, ...) . Quindi questo oggetto è chiamato metodo vincolato perché quando viene invocato esso sa di fornire l'oggetto a cui era associato come primo argomento. (Queste cose funzionano allo stesso modo in Python 2 e 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

Infine, Python ha metodi di classe e metodi statici - tipi speciali di metodi. I metodi di classe funzionano allo stesso modo dei metodi regolari, tranne che quando invocati su un oggetto si collegano alla classe dell'oggetto anziché all'oggetto. Quindi m.__self__ = type(a) . Quando chiamate tale metodo associato, passa la classe di a come primo argomento. I metodi statici sono ancora più semplici: non legano nulla e restituiscono semplicemente la funzione sottostante senza alcuna trasformazione.

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

Si noti che i metodi di classe sono associati alla classe anche quando si accede all'istanza:

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

Vale la pena notare che al livello più basso, le funzioni, i metodi, i metodi statici, ecc. Sono in realtà dei descrittori che invocano __get__ , __set __ e facoltativamente __del__ metodi speciali. Per maggiori dettagli su classmethods e staticmethods:

Classi vecchio stile e vecchio stile

Python 2.x 2.2.0

Le classi di nuovo stile sono state introdotte in Python 2.2 per unificare classi e tipi . Essi ereditano dal tipo di object livello superiore. Una classe di nuovo stile è un tipo definito dall'utente ed è molto simile ai tipi built-in.

# 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

Le classi vecchio stile non ereditano object . Le istanze di vecchio stile vengono sempre implementate con un tipo di instance incorporato.

# 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

In Python 3, le classi vecchio stile sono state rimosse.

Le classi di nuovo stile in Python 3 ereditano implicitamente object , quindi non è più necessario specificare MyClass(object) .

class MyClass:
    pass

my_inst = MyClass()

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

Valori predefiniti per variabili di istanza

Se la variabile contiene un valore di un tipo immutabile (ad es. Una stringa), allora va bene assegnare un valore predefinito come questo

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

È necessario prestare attenzione durante l'inizializzazione di oggetti mutabili come gli elenchi nel costruttore. Considera il seguente esempio:

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

Questo comportamento è causato dal fatto che in Python i parametri predefiniti sono vincolati all'esecuzione della funzione e non alla dichiarazione di funzione. Per ottenere una variabile di istanza predefinita che non è condivisa tra le istanze, si dovrebbe usare un costrutto come questo:

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 

Vedi anche Argomenti predefiniti mutabili e "Almost Astonishment" e l'argomento Mutable Default .

Eredità multipla

Python utilizza l'algoritmo di linearizzazione C3 per determinare l'ordine in cui risolvere gli attributi di classe, inclusi i metodi. Questo è noto come il metodo Resolution Order (MRO).

Ecco un semplice esempio:

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'

Ora, se istanziamo FooBar, se guardiamo l'attributo foo, vediamo che l'attributo di Foo viene trovato per primo

fb = FooBar()

e

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

Ecco il MRO di FooBar:

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

Si può semplicemente affermare che l'algoritmo MRO di Python è

  1. Profondità prima (es. FooBar quindi Foo ) a meno che
  2. un genitore condiviso ( object ) è bloccato da un bambino ( Bar ) e
  3. non sono consentite relazioni circolari.

Ad esempio, Bar non può ereditare da FooBar mentre FooBar eredita da Bar.

Per un esempio completo in Python, vedere la voce wikipedia .

Un'altra potente caratteristica nell'ereditarietà è super . super può recuperare le caratteristiche delle classi genitore.

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()

Eredità multipla con metodo init di classe, quando ogni classe ha il proprio metodo init, quindi proviamo per l'ineritanza multipla, quindi solo il metodo init viene chiamato di classe che è ereditato per primo.

per il seguente esempio, solo il metodo init della classe Foo viene chiamato come chiamata della classe Bar init

    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()

Produzione:

    foobar init
    foo init

Ma ciò non significa che la classe Bar non sia ereditaria. L'istanza della classe FooBar finale è anche un'istanza della classe Bar e della classe Foo .

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

Produzione:

True
True
True

Descrittori e ricerche punteggiate

I descrittori sono oggetti che sono (solitamente) attributi di classi e che hanno uno qualsiasi dei metodi speciali __get__ , __set__ o __delete__ .

I descrittori di dati hanno uno qualsiasi di __set__ o __delete__

Questi possono controllare la ricerca staticmethod su un'istanza e sono utilizzati per implementare funzioni, staticmethod , classmethod e property . Una ricerca tratteggiata (ad esempio, l'istanza foo della classe Foo osserva la bar attributi bar ovvero foo.bar ), utilizza il seguente algoritmo:

  1. bar è guardato in classe, Foo . Se è presente ed è un descrittore di dati , viene utilizzato il descrittore di dati. Ecco come la property è in grado di controllare l'accesso ai dati in un'istanza e le istanze non possono sovrascriverli. Se un descrittore di dati non è lì, allora

  2. bar viene cercata nell'istanza __dict__ . Questo è il motivo per cui possiamo eseguire l'override o bloccare i metodi chiamati da un'istanza con una ricerca punteggiata. Se la bar esiste nell'istanza, viene utilizzata. Altrimenti, noi allora

  3. guarda nella classe Foo per bar . Se si tratta di un descrittore , viene utilizzato il protocollo descrittore. In questo modo vengono implementate le funzioni (in questo contesto, metodi non classmethod ), classmethod e staticmethod . Altrimenti restituisce semplicemente l'oggetto lì, oppure esiste un AttributeError

Metodi di classe: inizializzatori alternativi

I metodi di classe presentano metodi alternativi per creare istanze di classi. Per illustrare, diamo un'occhiata a un esempio.

Supponiamo di avere una classe Person relativamente semplice:

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 + ".")

Potrebbe essere utile avere un modo per creare istanze di questa classe specificando un nome completo invece di nome e cognome separatamente. Un modo per farlo sarebbe quello di avere last_name essere un parametro facoltativo, e partendo dal presupposto che se non viene fornito, abbiamo passato il nome completo in:

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 + ".")

Tuttavia, ci sono due problemi principali con questo bit di codice:

  1. I parametri first_name e last_name ora sono fuorvianti, poiché è possibile immettere un nome completo per first_name . Inoltre, se ci sono più casi e / o più parametri che hanno questo tipo di flessibilità, la ramificazione if / elif / else può diventare fastidiosa velocemente.

  2. Non è altrettanto importante, ma vale comunque la pena segnalarlo: cosa succede se last_name è None , ma first_name non si divide in due o più cose tramite spazi? Abbiamo ancora un altro livello di convalida dell'input e / o gestione delle eccezioni ...

Inserisci i metodi di classe. Invece di avere un singolo inizializzatore, creeremo un inizializzatore separato, chiamato from_full_name , e lo classmethod decoratore classmethod (incorporato).

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 + ".")

Notate cls anziché self come primo argomento a from_full_name . I metodi di classe sono applicati alla classe generale, non un'istanza di una data classe (che è ciò che di solito indica il self ). Quindi, se cls è la nostra classe Person , il valore restituito dal metodo di classe from_full_name è Person(first_name, last_name, age) , che utilizza __init__ di Person per creare un'istanza della classe Person . In particolare, se dovessimo creare una sottoclasse Employee of Person , allora from_full_name funzionerebbe anche nella classe Employee .

Per dimostrare che funziona come previsto, creiamo istanze di Person in più di un modo senza la ramificazione in __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.

Altre referenze:

Composizione di classe

La composizione di classe consente relazioni esplicite tra oggetti. In questo esempio, le persone vivono in città che appartengono ai paesi. La composizione consente alle persone di accedere al numero di tutte le persone che vivono nel loro paese:

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

Patch per scimmia

In questo caso, "patch di scimmia" significa aggiungere una nuova variabile o un metodo a una classe dopo che è stata definita. Ad esempio, diciamo che abbiamo definito la classe A come

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

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

Ma ora vogliamo aggiungere un'altra funzione più avanti nel codice. Supponiamo che questa funzione sia la seguente.

def get_num(self):
    return self.num

Ma come aggiungiamo questo come metodo in A ? È semplice, in pratica, inseriamo semplicemente tale funzione in A con un'istruzione di assegnazione.

A.get_num = get_num

Perché funziona? Poiché le funzioni sono oggetti proprio come qualsiasi altro oggetto, e i metodi sono funzioni che appartengono alla classe.

La funzione get_num deve essere disponibile per tutte le esistenti (già create) anche per le nuove istanze di A

Queste aggiunte sono disponibili su tutte le istanze di quella classe (o delle sue sottoclassi) automaticamente. Per esempio:

foo = A(42)

A.get_num = get_num

bar = A(6);

foo.get_num() # 42

bar.get_num() # 6

Nota che, a differenza di altri linguaggi, questa tecnica non funziona per certi tipi built-in e non è considerata di buon stile.

Elenco di tutti i membri della classe

La funzione dir() può essere utilizzata per ottenere un elenco dei membri di una classe:

dir(Class)

Per esempio:

>>> 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']

È comune cercare solo membri "non magici". Questo può essere fatto usando una semplice comprensione che elenca membri con nomi che non iniziano con __ :

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

Avvertenze:

Le classi possono definire un __dir__() . Se questo metodo esiste chiamando dir() chiamerà __dir__() , altrimenti Python proverà a creare un elenco di membri della classe. Ciò significa che la funzione dir può avere risultati imprevisti. Due citazioni di importanza dalla documentazione ufficiale di Python :

Se l'oggetto non fornisce dir (), la funzione fa del suo meglio per raccogliere informazioni dall'attributo dict dell'oggetto, se definito, e dal suo oggetto type. L'elenco risultante non è necessariamente completo e potrebbe essere inaccurato quando l'oggetto ha un getattr personalizzato ().

Nota: poiché dir () viene fornito principalmente come utilità per l'uso a un prompt interattivo, tenta di fornire un set di nomi interessante più di quello che prova a fornire un insieme di nomi rigorosamente o coerentemente definito e il suo comportamento dettagliato può cambiare stampa. Ad esempio, gli attributi metaclass non sono nella lista dei risultati quando l'argomento è una classe.

Introduzione alle classi

Una classe, funziona come un modello che definisce le caratteristiche di base di un particolare oggetto. Ecco un esempio:

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))

Ci sono alcune cose da notare quando si guarda all'esempio sopra.

  1. La classe è composta da attributi (dati) e metodi (funzioni).
  2. Attributi e metodi sono semplicemente definiti come normali variabili e funzioni.
  3. Come indicato nella docstring corrispondente, il metodo __init__() è chiamato inizializzatore . È equivalente al costruttore in altri linguaggi orientati agli oggetti ed è il metodo che viene eseguito per la prima volta quando si crea un nuovo oggetto o una nuova istanza della classe.
  4. Gli attributi che si applicano all'intera classe sono definiti per primi e sono chiamati attributi di classe .
  5. Gli attributi che si applicano a un'istanza specifica di una classe (un oggetto) sono chiamati attributi di istanza . Sono generalmente definiti all'interno di __init__() ; questo non è necessario, ma è raccomandato (poiché gli attributi definiti al di fuori di __init__() corrono il rischio di essere consultati prima che vengano definiti).
  6. Ogni metodo, incluso nella definizione della classe, passa l'oggetto in questione come primo parametro. La parola self viene usata per questo parametro (l'uso di self è in realtà per convenzione, poiché la parola self non ha alcun significato intrinseco in Python, ma questa è una delle convenzioni più rispettate di Python e dovresti sempre seguirla).
  7. Quelli che sono abituati alla programmazione orientata agli oggetti in altre lingue possono essere sorpresi da alcune cose. Uno è che Python non ha alcun concetto reale di elementi private , quindi tutto, per impostazione predefinita, imita il comportamento della parola chiave public C ++ / Java. Per ulteriori informazioni, consultare l'esempio "Membri della classe privata" in questa pagina.
  8. Alcuni dei metodi della classe hanno la seguente forma: __functionname__(self, other_stuff) . Tutti questi metodi sono chiamati "metodi magici" e sono una parte importante delle classi in Python. Ad esempio, l'overloading dell'operatore in Python è implementato con metodi magici. Per ulteriori informazioni, consultare la documentazione pertinente .

Ora facciamo alcuni esempi della nostra classe Person !

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

Al momento abbiamo tre oggetti Person , kelly , joseph e john_doe .

Possiamo accedere agli attributi della classe da ogni istanza usando l'operatore punto . Notare nuovamente la differenza tra attributi di classe e istanza:

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

Possiamo eseguire i metodi della classe usando lo stesso operatore punto . :

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

Proprietà

Le classi Python supportano le proprietà , che assomigliano a variabili di oggetti regolari, ma con la possibilità di allegare comportamenti e documentazione personalizzati.

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

Sembra che l'oggetto della classe MyClass abbia una proprietà .string , tuttavia il suo comportamento è ora strettamente controllato:

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

Oltre alla sintassi utile come sopra, la sintassi della proprietà consente la convalida o altri incrementi da aggiungere a tali attributi. Questo potrebbe essere particolarmente utile con le API pubbliche - dove un livello di aiuto dovrebbe essere dato all'utente.

Un altro uso comune delle proprietà è di consentire alla classe di presentare "attributi virtuali" - attributi che non sono effettivamente memorizzati ma che vengono calcolati solo quando richiesto.

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 è un pattern che limita l'istanza di una classe a un'istanza / oggetto. Per maggiori informazioni sui modelli di design singleton Python, vedere qui .

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

Un altro metodo è quello di decorare la tua classe. Seguendo l'esempio di questa risposta, crea una 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)

Per usare puoi usare il metodo 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow