Promjenjivi i nepromjenjivi objekti u Pythonu - Vizualni i praktični vodič

Python je sjajan jezik. Zbog jednostavnosti, mnogi ga odabiru kao svoj prvi programski jezik.

Iskusni programeri također stalno koriste Python, zahvaljujući širokoj zajednici, obilju paketa i jasnoj sintaksi.

No, postoji jedno pitanje koje kao da zbunjuje početnike kao i neke iskusne programere: Python objekti. Točnije, razlika između promjenjivih i nepromjenjivih predmeta.

U ovom ćemo postu produbiti znanje o Python objektima, naučiti razliku između promjenjivih i nepromjenjivih objekata i vidjeti kako možemo koristiti tumač da bismo bolje razumjeli kako Python djeluje.

Upotrijebit ćemo važne funkcije i ključne riječi kao što su idi is, i razumjet ćemo razliku između x == yi x is y.

Jeste li za to? Započnimo.

U Pythonu je sve objekt

Za razliku od ostalih programskih jezika u kojima jezik podržava objekte, u Pythonu je doista sve objekt - uključujući cijele brojeve, popise, pa čak i funkcije.

Pomoću našeg tumača možemo provjeriti da:

>>> isinstance(1, object) True >>> isinstance(False, object) True def my_func(): return "hello" >>> isinstance(my_func, object) True

Python ima ugrađenu funkciju idkoja vraća adresu objekta u memoriji. Na primjer:

>>> x = 1 >>> id(x) 1470416816

Iznad smo stvorili objekt s imenom xi dodijelili mu vrijednost 1. Zatim smo koristili id(x)i otkrili da se ovaj objekt nalazi na adresi 1470416816u memoriji.

To nam omogućuje provjeru zanimljivosti o Pythonu. Recimo da u Pythonu stvorimo dvije varijable - jednu po imenu x, a drugu po imenu y- i dodijelimo im istu vrijednost. Na primjer, ovdje:

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

Možemo upotrijebiti operator jednakosti ( ==) da provjerimo imaju li doista jednaku vrijednost u Pythonovim očima:

>>> x == y True

No jesu li to isti objekti u sjećanju? U teoriji ovdje mogu postojati dva vrlo različita scenarija.

Prema scenariju (1) , stvarno imamo dva različita objekta, jedan po imenu x, a drugi po imenu y, koji jednostavno imaju istu vrijednost.

Ipak, to bi također mogao biti slučaj da Python ovdje zapravo pohranjuje samo jedan objekt koji ima dva imena koja ga upućuju - kao što je prikazano u scenariju (2) :

Možemo upotrijebiti idgore predstavljenu funkciju da to provjerimo:

>>> x = "I love Python!" >>> y = "I love Python!" >>> x == y True >>> id(x) 52889984 >>> id(y) 52889384

Kao što vidimo, Pythonovo ponašanje se poklapa sa gore opisanim scenarijem (1). Iako su x == yu ovom primjeru (to jest xi yimaju iste vrijednosti ), to su različiti objekti u memoriji. To je zato što id(x) != id(y), kao što izričito možemo provjeriti:

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

Postoji kraći način za usporedbu gore, a to je korištenje Pythonovog isoperatora. Provjera je li x is yisto što i provjera id(x) == id(y), što znači jesu li xi yjesu li isti objekt u memoriji:

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

Ovo osvjetljava važnu razliku između operatora jednakosti i operatora ==identiteta is.

Kao što možete vidjeti u gornjem primjeru, potpuno je moguće da se dva imena u Pythonu ( xi y) vežu na dva različita objekta (a samim tim i x is yjest False), pri čemu ova dva objekta imaju istu vrijednost (takva x == yje True).

Kako možemo stvoriti drugu varijablu koja upućuje na isti objekt na koji xpokazuje? Jednostavno možemo koristiti operator dodjele =, na sljedeći način:

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

Da bismo provjerili pokazuju li doista isti objekt, možemo upotrijebiti isoperator:

>>> x is z True

Naravno, to znači da imaju istu adresu u memoriji, što možemo izričito provjeriti pomoću id:

>>> id(x) 54221824 >>> id(z) 54221824

I, naravno, imaju istu vrijednost, pa očekujemo x == zi povratak True:

>>> x == z True

Promjenjivi i nepromjenjivi objekti u Pythonu

Rekli smo da je sve u Pythonu objekt, ali ipak postoji važna razlika između objekata. Neki su objekti promjenjivi, dok su neki nepromjenjivi .

Kao što sam već spomenuo, ova činjenica uzrokuje zbunjenost kod mnogih ljudi koji su novi u Pythonu, pa ćemo se pobrinuti da je to jasno.

Nepromjenjivi objekti u Pythonu

Za neke tipove u Pythonu, nakon što stvorimo instance tih tipova, one se nikada neće promijeniti. Oni su nepromjenjivi .

Na primjer, intobjekti su nepromjenjivi u Pythonu. Što će se dogoditi ako pokušamo promijeniti vrijednost intpredmeta?

>>> x = 24601 >>> x 24601 >>> x = 24602 >>> x 24602

Pa, čini se da smo se xuspješno promijenili . Tu se mnogi ljudi zbune. Što se točno ovdje dogodilo ispod haube? Iskoristimo idza daljnje istraživanje:

>>> x = 24601 >>> x 24601 >>> id(x) 1470416816 >>> x = 24602 >>> x 24602 >>> id(x) 1470416832

Dakle, možemo vidjeti da dodjeljivanjem x = 24602nismo promijenili vrijednost predmeta na koji smo xprije bili vezani. Umjesto toga, stvorili smo novi objekt i za njega povezali ime x.

Tako je nakon dodjele 24601se xpomoću x = 24601imali smo sljedeće stanje:

Nakon upotrebe x = 24602stvorili smo novi objekt i ime povezali xs tim novim objektom. Drugi objekt čija je vrijednost 24601više nije dostupan x(ili bilo koje drugo ime u ovom slučaju):

Whenever we assign a new value to a name (in the above example - x) that is bound to an int object, we actually change the binding of that name to another object.

The same applies for tuples, strings (str objects), and bools as well. In other words, int (and other number types such as float), tuple, bool, and str objects are immutable.

Let's test this hypothesis. What happens if we create a tuple object, and then give it a different value?

>>> my_tuple = (1, 2, 3) >>> id(my_tuple) 54263304 >>> my_tuple = (3, 4, 5) >>> id(my_tuple) 56898184

Just like an int object, we can see that our assignment actually changed the object that the name my_tuple is bound to.

What happens if we try to change one of the tuple's elements?

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

As we can see, Python doesn't allow us to modify my_tuple's contents, as it is immutable.

Mutable objects in Python

Some types in Python can be modified after creation, and they are called mutable. For example, we know that we can modify the contents of a list object:

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

Does that mean we actually created a new object when assigning a new value to the first element of my_list? Again, we can use id to check:

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

So our first assignment my_list = [1, 2, 3] created an object in the address 55834760, with the values of 1, 2, and 3:

We then modified the first element of this list object using my_list[0] = 'a new value', that is - without creating a new list object:

Now, let us create two names – x and y, both bound to the same list object. We can verify that either by using is, or by explicitly checking their ids:

>>> x = y = [1, 2] >>> x is y True >>> id(x) 18349096 >>> id(y) 18349096 >>> id(x) == id(y) True

What happens now if we use x.append(3)? That is, if we add a new element (3) to the object by the name of x?

Will x by changed? Will y?

Well, as we already know, they are basically two names of the same object:

Since this object is changed, when we check its names we can see the new value:

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

Note that x and y have the same id as before – as they are still bound to the same list object:

>>> id(x) 18349096 >>> id(y) 18349096

In addition to lists, other Python types that are mutable include sets and dicts.

Implications for dictionary keys in Python

Dictionaries (dict objects) are commonly used in Python. As a quick reminder, we define them like so:

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

We can then access a specific element by its key name:

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

Dictionaries are mutable, so we can change their content after creation. At any given moment, a key in the dictionary can point to one element only:

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

It is interesting to note that a dictionary's keys must be immutable:

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

Why is that so?

Let's consider the following hypothetical scenario (note: the snippet below can't really be run in Python):

>>> x = [1, 2] >>> y = [1, 2, 3] >>> my_dict = {x: 'a', y: 'b'}

So far, things don't seem that bad. We'd assume that if we access my_dict with the key of [1, 2], we will get the corresponding value of 'a', and if we access the key [1, 2, 3], we will get the value 'b'.

Now, what would happen if we attempted to use:

>>> x.append(3)

In this case, x would have the value of [1, 2, 3], and y would also have the value of [1, 2, 3]. What should we get when we ask for my_dict[[1, 2, 3]]? Will it be 'a' or 'b'? To avoid such cases, Python simply doesn't allow dictionary keys to be mutable.

Taking things a bit further

Let's try to apply our knowledge to a case that is a bit more interesting.

Below, we define a list (a mutable object) and a tuple (an immutable object). The list includes a tuple, and the tuple includes a list:

>>> my_list = [(1, 1), 2, 3] >>> my_tuple = ([1, 1], 2, 3) >>> type(my_list)  >>> type(my_list[0])  >>> type(my_tuple)  >>> type(my_tuple[0]) 

So far so good. Now, try to think for yourself – what will happen when we try to execute each of the following statements?

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

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

In statement (1), what we are trying to do is change my_list's first element, that is, a tuple. Since a tuple is immutable, this attempt is destined to fail:

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

Note that what we were trying to do is not change the list, but rather – change the contents of its first element.

Let's consider statement (2). In this case, we are accessing my_tuple's first element, which happens to be a list, and modify it. Let's further investigate this case and look at the addresses of these elements:

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

When we change my_tuple[0][0], we do not really change my_tuple at all! Indeed, after the change, my_tuple's first element will still be the object whose address in memory is 20446248. We do, however, change the value of that object:

>>> my_tuple[0][0] = 'Changed!' >>> id(my_tuple) 20551816 >>> id(my_tuple[0]) 20446248 >>> my_tuple (['Changed!', 1], 2, 3)

Since we only modified the value of my_tuple[0], which is a mutable list object, this operation was indeed allowed by Python.

Recap

In this post we learned about Python objects. We said that in Python everything is an object, and got to use id and is to deepen our understanding of what's happening under the hood when using Python to create and modify objects.

We also learned the difference between mutable objects, that can be modified after creation, and immutable objects, which cannot.

We saw that when we ask Python to modify an immutable object that is bound to a certain name, we actually create a new object and bind that name to it.

We then learned why dictionary keys have to be immutable in Python.

Understanding how Python "sees" objects is a key to becoming a better Python programmer. I hope this post has helped you on your journey to mastering Python.

Omer Rosenbaum, Swimm’s Chief Technology Officer. Cyber training expert and Founder of Checkpoint Security Academy. Author of Computer Networks (in Hebrew). Visit My YouTube Channel.