Szukaj…


Zmienny vs Niezmienny

Istnieją dwa rodzaje typów w Pythonie. Niezmienne i zmienne typy.

Niezmienne

Obiekt typu niezmiennego nie może zostać zmieniony. Każda próba modyfikacji obiektu spowoduje utworzenie kopii.

Ta kategoria obejmuje: liczby całkowite, zmiennoprzecinkowe, złożone, łańcuchy, bajty, krotki, zakresy i frozensety.

Aby podświetlić tę właściwość, zagrajmy z wbudowanym id . Ta funkcja zwraca unikalny identyfikator obiektu przekazany jako parametr. Jeśli identyfikator jest taki sam, jest to ten sam obiekt. Jeśli się zmieni, to jest to kolejny obiekt. (Niektórzy twierdzą, że tak naprawdę jest to adres pamięci obiektu, ale strzeżcie się ich, pochodzą z ciemnej strony siły ...)

>>> a = 1
>>> id(a)
140128142243264
>>> a += 2
>>> a
3
>>> id(a)
140128142243328

Okej, 1 to nie 3 ... Najnowsze wiadomości ... Może nie. Jednak takie zachowanie jest często zapominane, jeśli chodzi o bardziej złożone typy, zwłaszcza ciągi znaków.

>>> stack = "Overflow"
>>> stack
'Overflow'
>>> id(stack)
140128123955504
>>> stack += " rocks!"
>>> stack
'Overflow rocks!'

Aha! Widzieć? Możemy to zmienić!

>>> id(stack)
140128123911472

Nie. Chociaż wydaje się, że możemy zmienić ciąg nazwany przez stack zmiennych, tak naprawdę to robimy, tworząc nowy obiekt, który będzie zawierał wynik konkatenacji. Jesteśmy oszukani, ponieważ w tym czasie stary obiekt nigdzie nie idzie, więc zostaje zniszczony. W innej sytuacji byłoby to bardziej oczywiste:

>>> stack = "Stack"
>>> stackoverflow = stack + "Overflow"
>>> id(stack)
140128069348184
>>> id(stackoverflow)
140128123911480

W tym przypadku jasne jest, że jeśli chcemy zachować pierwszy ciąg, potrzebujemy kopii. Ale czy to takie oczywiste w przypadku innych typów?

Ćwiczenie

Teraz, wiedząc, jak działają typy niezmienne, co byś powiedział z poniższym fragmentem kodu? Czy to jest mądre?

s = ""
for i in range(1, 1000):
    s += str(i)
    s += ","

Mutables

Obiekt typu zmiennego można zmienić i zmienić na miejscu . Nie wykonuje się żadnych niejawnych kopii.

Ta kategoria obejmuje: listy, słowniki, bajtary i zestawy.

Grajmy dalej z naszą małą funkcją id .

>>> b = bytearray(b'Stack')
>>> b
bytearray(b'Stack')
>>> b = bytearray(b'Stack')
>>> id(b)
140128030688288
>>> b += b'Overflow'
>>> b
bytearray(b'StackOverflow')
>>> id(b)
140128030688288

(Na marginesie, używam bajtów zawierających dane ascii, aby wyjaśnić moje zdanie, ale pamiętaj, że bajty nie są zaprojektowane do przechowywania danych tekstowych. Niech mi wybaczy.)

Co my mamy? Tworzymy bytearray, modyfikujemy go i używając id możemy zapewnić, że jest to ten sam obiekt, zmodyfikowany. To nie jest kopia.

Oczywiście, jeśli obiekt będzie często modyfikowany, typ zmienny wykonuje znacznie lepszą pracę niż typ niezmienny. Niestety, rzeczywistość tej nieruchomości jest często zapominana, gdy najbardziej boli.

>>> c = b
>>> c += b' rocks!'
>>> c
bytearray(b'StackOverflow rocks!')

W porządku...

>>> b
bytearray(b'StackOverflow rocks!')

Chwileczkę ...

>>> id(c) == id(b)
True

W rzeczy samej. c nie jest kopią b . c jest b .

Ćwiczenie

Teraz lepiej zrozumiesz, jaki efekt uboczny implikuje zmienny typ. Czy możesz wyjaśnić, co dzieje się nie tak w tym przykładzie?

>>> ll = [ [] ]*4 # Create a list of 4 lists to contain our results
>>> ll
[[], [], [], []]
>>> ll[0].append(23) # Add result 23 to first list
>>> ll
[[23], [23], [23], [23]]
>>> # Oops...

Zmienne i niezmienne jako argumenty

Jednym z głównych przypadków użycia, gdy deweloper musi wziąć pod uwagę zmienność, jest przekazywanie argumentów do funkcji. Jest to bardzo ważne, ponieważ określi to zdolność funkcji do modyfikowania obiektów, które nie należą do jej zakresu, lub innymi słowy, jeśli funkcja ma skutki uboczne. Ważne jest również, aby zrozumieć, gdzie należy udostępnić wynik funkcji.

>>> def list_add3(lin):
    lin += [3]
    return lin

>>> a = [1, 2, 3]
>>> b = list_add3(a)
>>> b
[1, 2, 3, 3]
>>> a
[1, 2, 3, 3]

Błąd polega na tym, że lin , jako parametr funkcji, można modyfikować lokalnie. Zamiast lin i odniesienia tego samego obiektu. a Ponieważ ten obiekt można modyfikować, modyfikacja jest wykonywana w miejscu, co oznacza, że obiekt, do którego odwołuje się zarówno lin jak i a jest modyfikowany. lin naprawdę nie muszą być zwrócone, ponieważ już mamy odniesienie do tego obiektu w postaci . a a i b odnoszą się do tego samego obiektu.

To samo nie dotyczy krotek.

>>> def tuple_add3(tin):
    tin += (3,)
    return tin

>>> a = (1, 2, 3)
>>> b = tuple_add3(a)
>>> b
(1, 2, 3, 3)
>>> a
(1, 2, 3)

Na początku funkcji, tin oraz a odniesieniu do tego samego obiektu. Ale to niezmienny obiekt. Więc gdy funkcja próbuje go zmodyfikować, tin otrzymać nowy obiekt z modyfikacją, a utrzymuje odniesienie do oryginalnego obiektu. a W takim przypadku zwrot tin jest obowiązkowy, w przeciwnym razie nowy obiekt zostanie utracony.

Ćwiczenie

>>> def yoda(prologue, sentence):
    sentence.reverse()
    prologue += " ".join(sentence)
    return prologue

>>> focused = ["You must", "stay focused"]
>>> saying = "Yoda said: "
>>> yoda_sentence = yoda(saying, focused)

Uwaga: reverse działa w miejscu.

Co sądzisz o tej funkcji? Czy to ma skutki uboczne? Czy zwrot jest konieczny? Po zakończeniu rozmowy, co jest wartością saying ? focused ? Co się stanie, jeśli funkcja zostanie ponownie wywołana z tymi samymi parametrami?



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow