Python este un limbaj minunat. Datorită simplității sale, mulți oameni îl aleg ca primul lor limbaj de programare.

Programatorii cu experiență folosesc Python tot timpul, datorită comunității sale largi, abundenței pachetelor și sintaxei clare.

Dar există o problemă care pare să încurce atât începătorii, cât și unii dezvoltatori experimentați: obiectele Python. Mai exact, diferența dintre mutabil și imuabil obiecte.

În această postare ne vom aprofunda cunoștințele despre obiectele Python, vom învăța diferența dintre mutabil și imuabil obiecte și să vedem cum putem folosi interpret pentru a înțelege mai bine cum funcționează Python.

Vom folosi funcții importante și cuvinte cheie precum id și is, și vom înțelege diferența dintre x == y și x is y.

Ești pregătit pentru? Să începem.

În Python, totul este un obiect

Spre deosebire de alte limbaje de programare în care limbajul suporturi obiecte, în Python într-adevăr Tot este un obiect – inclusiv numere întregi, liste și chiar funcții.

Putem folosi interpretul nostru pentru a verifica dacă:

>>> isinstance(1, object)
True

>>> isinstance(False, object)
True

def my_func():
   return "hello"
   
>>> isinstance(my_func, object)
True
Totul din Python este un obiect

Python are o funcție încorporată, id, care returnează adresa unui obiect în memorie. De exemplu:

>>> x = 1
>>> id(x)
1470416816
Functia id(obj) returnează adresa de obj in memoria

Mai sus, am creat un obiect în numele lui x, și i-a atribuit valoarea 1. Am folosit apoi id(x) și am descoperit că acest obiect se găsește la adresa 1470416816 in memoria.

Acest lucru ne permite să verificăm lucruri interesante despre Python. Să presupunem că creăm două variabile în Python – una cu numele de xși unul cu numele de y – și atribuiți-le aceeași valoare. De exemplu, aici:

>>> x = "I love Python!"
>>> y = "I love Python!"

Putem folosi operatorul de egalitate (==) pentru a verifica dacă într-adevăr au aceeași valoare în ochii lui Python:

>>> x == y
True

Dar acestea sunt același obiect din memorie? În teorie, aici pot exista două scenarii foarte diferite.

Conform scenariului (1), avem într-adevăr două obiecte diferite, unul cu numele de x, și altul cu numele de y, care se întâmplă să aibă aceeași valoare.

Totuși, ar putea fi și cazul în care Python stochează aici de fapt doar un singur obiect, care are două nume care îl referă – așa cum se arată în scenariu (2):

Obiecte mutabile vs imuabile in Python Un ghid vizual

Putem folosi id funcție introdusă mai sus pentru a verifica acest lucru:

>>> x = "I love Python!"
>>> y = "I love Python!"
>>> x == y
True

>>> id(x)
52889984

>>> id(y)
52889384

Așa cum putem vedea, comportamentul lui Python se potrivește cu scenariul (1) descris mai sus. Chiar dacă x == y în acest exemplu (adică x și y au aceleași valori), sunt obiecte diferite în memorie. Asta pentru ca id(x) != id(y), după cum putem verifica în mod explicit:

>>> id(x) == id(y)
False

Există o modalitate mai scurtă de a face comparația de mai sus, și anume folosind Python’s is operator. Verificarea dacă x is y este la fel ca verificarea id(x) == id(y), ceea ce înseamnă dacă x și y sunt același obiect în memorie:

>>> x == y
True

>>> id(x) == id(y)
False

>>> x is y
False

Acest lucru aruncă o lumină asupra diferenței importante dintre operatorul egalității == și operatorul de identitate is.

După cum puteți vedea în exemplul de mai sus, este complet posibil pentru două nume în Python (x și y) să fie legat de două obiecte diferite (și astfel, x is y este False), unde aceste două obiecte au aceeași valoare (deci x == y este True).

Cum putem crea o altă variabilă care indică același obiect pe care x arata spre? Putem folosi pur și simplu operatorul de atribuire =, ca astfel:

>>> x = "I love Python!"
>>> z = x

Pentru a verifica dacă într-adevăr indică același obiect, putem folosi is operator:

>>> x is z
True

Desigur, acest lucru înseamnă că au aceeași adresă în memorie, așa cum putem verifica în mod explicit folosind id:

>>> id(x)
54221824

>>> id(z)
54221824

Și, desigur, au aceeași valoare, așa că ne așteptăm x == z a se intoarce True de asemenea:

>>> x == z
True

Obiecte mutabile și imuabile în Python

Am spus că totul în Python este un obiect, totuși există o distincție importantă între obiecte. Unele obiecte sunt mutabil în timp ce unii sunt imuabil.

După cum am menționat anterior, acest fapt provoacă confuzie pentru mulți oameni care sunt noi în Python, așa că ne vom asigura că este clar.

Obiecte imuabile în Python

Pentru unele tipuri din Python, odată ce am creat instanțe ale acestor tipuri, acestea nu se schimbă niciodată. Sunt imuabil.

De exemplu, int obiectele sunt imuabile în Python. Ce se va întâmpla dacă încercăm să schimbăm valoarea unui int obiect?

>>> x = 24601
>>> x
24601

>>> x = 24602
>>> x
24602

Ei bine, se pare că ne-am schimbat x cu succes. Exact aici se confundă mulți oameni. Ce s-a întâmplat exact sub capotă aici? Să folosim id pentru a investiga în continuare:

>>> x = 24601
>>> x
24601

>>> id(x)
1470416816

>>> x = 24602
>>> x
24602

>>> id(x)
1470416832

Deci, putem vedea asta prin atribuire x = 24602, nu am schimbat valoarea obiectului care x fusese legat până acum. Mai degrabă, am creat un obiect nou și am legat numele x la ea.

Deci, după atribuire 24601 la x prin utilizarea x = 24601, am avut următoarea stare:

1611600248 186 Obiecte mutabile vs imuabile in Python Un ghid vizual

Și după utilizare x = 24602, am creat un nou obiect și am legat numele x la acest nou obiect. Celălalt obiect cu valoarea lui 24601 nu mai este accesibil prin x (sau orice alt nume în acest caz):

1611600248 64 Obiecte mutabile vs imuabile in Python Un ghid vizual

Ori de câte ori atribuim o nouă valoare unui nume (în exemplul de mai sus – x) care este legat de un int obiect, schimbăm de fapt legarea acelui nume la un alt obiect.

Același lucru este valabil și pentru tuples, corzi (str obiecte) și bools, de asemenea. Cu alte cuvinte, int (și alte tipuri de numere, cum ar fi float), tuple, bool, și str obiectele sunt imuabil.

Să testăm această ipoteză. Ce se întâmplă dacă creăm un tuple obiect și apoi să-i dai o valoare diferită?

>>> my_tuple = (1, 2, 3)
>>> id(my_tuple)
54263304

>>> my_tuple = (3, 4, 5)
>>> id(my_tuple)
56898184

La fel ca un int obiect, putem vedea că misiunea noastră a schimbat de fapt obiectul pe care îl numea my_tuple este legat de.

Ce se întâmplă dacă încercăm să schimbăm unul dintre tupleelementele?

>>> my_tuple[0] = 'a new value'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

După cum putem vedea, Python nu ne permite să modificăm my_tupleconținutul său, deoarece este imuabil.

Obiecte mutabile în Python

Unele tipuri din Python pot fi modificate după creare și sunt numite mutabil. De exemplu, știm că putem modifica conținutul unui list obiect:

>>> my_list = [1, 2, 3]
>>> my_list[0] = 'a new value'
>>> my_list
['a new value', 2, 3]

Asta înseamnă că am creat de fapt un nou obiect atunci când atribuim o nouă valoare primului element al my_list? Din nou, putem folosi id a verifica:

>>> my_list = [1, 2, 3]
>>> id(my_list)
55834760

>>> my_list
[1, 2, 3]

>>> my_list[0] = 'a new value'
>>> id(my_list)
55834760

>>> my_list
['a new value', 2, 3]

Deci prima noastră sarcină my_list = [1, 2, 3] a creat un obiect în adresă 55834760, cu valorile lui 1, 2, și 3:

1611600248 523 Obiecte mutabile vs imuabile in Python Un ghid vizual

Am modificat apoi primul element al acestui lucru list obiect folosind my_list[0] = 'a new value', adică – fără a crea un nou list obiect:

1611600248 142 Obiecte mutabile vs imuabile in Python Un ghid vizual

Acum, haideți să creăm două nume – x și y, ambele legate de același lucru list obiect. Putem verifica acest lucru fie folosind is, sau prin verificarea explicită a acestora ids:

>>> x = y = [1, 2]
>>> x is y
True

>>> id(x)
18349096

>>> id(y)
18349096

>>> id(x) == id(y)
True

Ce se întâmplă acum dacă folosim x.append(3)? Adică, dacă adăugăm un element nou (3) către obiectul cu numele de x?

Voi x prin schimbat? Voi y?

Ei bine, după cum știm deja, acestea sunt în principiu două nume ale aceluiași obiect:

1611600248 508 Obiecte mutabile vs imuabile in Python Un ghid vizual

Deoarece acest obiect este schimbat, când îi verificăm numele, putem vedea noua valoare:

>>> x.append(3)
>>> x
[1, 2, 3]

>>> y
[1, 2, 3]

Rețineți că x și y au aceleași id ca și înainte – întrucât sunt încă legați de același lucru list obiect:

>>> id(x)
18349096

>>> id(y)
18349096
1611600248 664 Obiecte mutabile vs imuabile in Python Un ghid vizual

Pe lângă lists, alte tipuri Python care pot fi modificate includ sets și dicts.

Implicații pentru cheile de dicționar în Python

Dicționare (dict obiecte) sunt utilizate în mod obișnuit în Python. Ca un memento rapid, le definim astfel:

my_dict = {"name": "Omer", "number_of_pets": 1}

Putem apoi accesa un anumit element prin numele cheii sale:

>>> my_dict["name"]
'Omer'

Dicționarele sunt mutabil, astfel încât să le putem schimba conținutul după creare. În orice moment, o cheie din dicționar poate indica un singur element:

>>> my_dict["name"] = "John"
>>> my_dict["name"]
'John'

Este interesant de observat că a cheile dicționarului trebuie să fie imuabile:

>>> my_dict = {[1,2]: "Hello"}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

De ce este așa?

Să luăm în considerare următorul scenariu ipotetic (notă: fragmentul de mai jos nu poate fi rulat într-adevăr în Python):

>>> x = [1, 2]
>>> y = [1, 2, 3]
>>> my_dict = {x: 'a', y: 'b'}
Acesta este un caz ipotetic care nu poate fi rulat cu adevărat în Python

Până acum, lucrurile nu par atât de rele. Am presupune asta dacă accesăm my_dict cu cheia de [1, 2], vom obține valoarea corespunzătoare a 'a', și dacă accesăm cheia [1, 2, 3], vom obține valoarea 'b'.

Acum, ce s-ar întâmpla dacă am încerca să folosim:

>>> x.append(3)

În acest caz, x ar avea valoarea de [1, 2, 3], și y ar avea și valoarea de [1, 2, 3]. Ce ar trebui să obținem atunci când cerem my_dict[[1, 2, 3]]? Va fi 'a' sau 'b'? Pentru a evita astfel de cazuri, Python pur și simplu nu permite modificarea cheilor din dicționar.

Luând lucrurile puțin mai departe

Să încercăm să ne aplicăm cunoștințele într-un caz puțin mai interesant.

Mai jos, definim un list (A mutabil obiect) și a tuple (un imuabil obiect). list include un tuple, si tuple include un list:

>>> my_list = [(1, 1), 2, 3]
>>> my_tuple = ([1, 1], 2, 3)
>>> type(my_list)
<class 'list'>

>>> type(my_list[0])
<class 'tuple'>

>>> type(my_tuple)
<class 'tuple'>

>>> type(my_tuple[0])
<class 'list'>

Până acum, bine. Acum, încearcă să te gândești singur – ce se va întâmpla când încercăm să executăm fiecare dintre următoarele afirmații?

(1) >>> my_list[0][0] = 'Changed!'

(2) >>> my_tuple[0][0] = 'Changed!'

În declarația (1), ceea ce încercăm să facem este să ne schimbăm my_listprimul element, adică a tuple. Din moment ce a tuple este imuabil, această încercare este destinată eșecului:

>>> my_list[0][0] = 'Changed!'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Rețineți că ceea ce încercam să facem este nu schimbați lista, ci mai degrabă – schimbați conținutul primului său element.

Să luăm în considerare afirmația (2). În acest caz, accesăm my_tupleprimul element, care se întâmplă să fie un list, și modificați-l. Să investigăm în continuare acest caz și să analizăm adresele acestor elemente:

>>> my_tuple = ([1, 1], 2, 3)
>>> id(my_tuple)
20551816

>>> type(my_tuple[0])
<class 'list'>

>>> id(my_tuple[0])
20446248
1611600249 486 Obiecte mutabile vs imuabile in Python Un ghid vizual

Când ne schimbăm my_tuple[0][0], nu ne schimbăm cu adevărat my_tuple deloc! Într-adevăr, după schimbare, my_tuplePrimul element va fi în continuare obiectul a cărui adresă este în memorie 20446248. Cu toate acestea, schimbăm valoarea acelui obiect:

>>> my_tuple[0][0] = 'Changed!'
>>> id(my_tuple)
20551816

>>> id(my_tuple[0])
20446248

>>> my_tuple
(['Changed!', 1], 2, 3)
Ambii id(my_tuple) și id(my_tuple[0]) rămâne la fel după schimbare
1611600249 830 Obiecte mutabile vs imuabile in Python Un ghid vizual

Deoarece am modificat doar valoarea lui my_tuple[0], care este un mutabil list obiect, această operațiune a fost într-adevăr permisă de Python.

Recapitulare

În această postare am aflat despre obiectele Python. Am spus asta în Python totul este un obiect, și a ajuns să folosesc id și is pentru a ne aprofunda înțelegerea a ceea ce se întâmplă sub capotă atunci când folosim Python pentru a crea și modifica obiecte.

De asemenea, am învățat diferența dintre mutabil obiecte, care pot fi modificate după creare și imuabil obiecte, care nu pot.

Am văzut că atunci când îi cerem lui Python să modifice un obiect imuabil care este legat de un anumit nume, de fapt creăm un obiect nou și îl legăm de acesta.

Am aflat apoi de ce trebuie să fie cheile de dicționar imuabil în Python.

Înțelegerea modului în care Python „vede” obiectele este o cheie pentru a deveni un programator Python mai bun. Sper că această postare te-a ajutat în călătoria ta către stăpânirea Python.

Omer Rosenbaum, ÎnotDirector tehnologic. Expert în formare cibernetică și fondator al Checkpoint Security Academy. Autor al Rețele de calculatoare (în ebraică). Vizitează-l pe My Canalul canalului YouTube.