Време је за другу лекцију о типовима, где ћу писати о колекцијама података у Python програмском језику.

Ако нисте прочитали прву лекцију, то можете учинити на овом линку.

Колекције представљају контејнере који служе за складиштење више вредности. У овом чланку ћу се оријентисати на dict тип и како се промењиве складиште у меморији, док ћу у следећем наставити о колекцијама, конкретно о list, tuple и set типовима.

dict

dict (речник) је тип колекције који чува парове кључ-вредност. Испод можете видети пример креирања речника:

person = {
    'first_name': 'Kosta',
    'last_name': 'Kupresak',
    'job_title': 'Software Developer',
}

print(person)

Испис:

{'first_name': 'Kosta', 'last_name': 'Kupresak', 'job_title': 'Software Developer'}

Као што сам већ рекао, речинк чува парове кључ-вредност. Ипак, могуће је приступити само кључевима или пак само вредностима:

person = {
    'first_name': 'Kosta',
    'last_name': 'Kupresak',
    'job_title': 'Software Developer',
}

print(f'Keys: {person.keys()}')
print(f'Values: {person.values()}')

Испис:

Keys: dict_keys(['first_name', 'last_name', 'job_title'])
Values: dict_values(['Kosta', 'Kupresak', 'Software Developer'])

А, ако желимо приступити само једној вредности то можемо урадити преко кључа:

person = {
    'first_name': 'Kosta',
    'last_name': 'Kupresak',
    'job_title': 'Software Developer',
}

print(person['first_name'])

Испис:

Kosta

Ипак, овај начин приступа није најсигурнији, јер ако кључ не постоји у речнику добићемо KeyError грешку. Нпр:

person = {
    'first_name': 'Kosta',
    'last_name': 'Kupresak',
    'job_title': 'Software Developer',
}

print(person['address'])

Испис:

Traceback (most recent call last):
  File "C:/Users/me/PycharmProjects/examples/main.py", line 7, in <module>
    print(person['address'])
KeyError: 'address'

Наравно, постоји доста сигурнији приступ, где осим кључа, чију вредност тражимо, постављамо подразумевану вредност. У том случају, Python ће прво покушати да пронађе кључ и врати вредност под тим кључем, а у случају KeyError изузетка вратиће подразумевану вредност. Овим начином се осигуравамо да не дође до пуцања програма за време run-time-а.

person = {
    'first_name': 'Kosta',
    'last_name': 'Kupresak',
    'job_title': 'Software Developer',
}

print(person.get('address', 'No data'))

Испис:

No data

Да бисмо додали нову вредност у речинк довољно је да је поставимо преко кључа. Ако кључ постоји, тренутна вредност ће бити преписана, док ако кључ не постоји у речник ће се додати нови пар кључ-вредност.

person = {
    'first_name': 'Kosta',
    'last_name': 'Kupresak',
    'job_title': 'Software Developer',
}

person['address'] = 'Novi Sad'  # Речник ће примити нови пар кључ-вредност
print(person.get('address', 'No data'))

person['address'] = 'Novi Sad 21000'  # Речник ће постојећу вредност под кључим address преписати
print(person)

Испис:

Novi Sad
{'first_name': 'Kosta', 'last_name': 'Kupresak', 'job_title': 'Software Developer', 'address': 'Novi Sad 21000'}

Да бисмо проверили дужину речника, односно колико он садржи парова кључ-вредност, можемо искористити функцију len(). За уклањање пара кључ-вредност преко кључа постоји метода pop(), док уклањање последњег доданог пара вршимо методом popitem().

Напомена: метода popitem() је у верзијама пре Python-а 3.7 уклањала насумичан пар кључ-вредност.

person = {
    'first_name': 'Kosta',
    'last_name': 'Kupresak',
    'job_title': 'Software Developer',
    'address': 'Novi Sad',
    'country': 'Serbia',
    'blog': 'kostakuu.rs',
}

print(len(person))

person.pop('address')
print(person)

person.popitem()
print(f'Речинк {person} је дужине {len(person)}')

person.clear()  # Могуће је уклонити и све парове кључ-вредност из речника у једном позиву
print(f'После позива методе clear(): {person}')

Испис:

6
{'first_name': 'Kosta', 'last_name': 'Kupresak', 'job_title': 'Software Developer', 'country': 'Serbia', 'blog': 'kostakuu.rs'}
Речинк {'first_name': 'Kosta', 'last_name': 'Kupresak', 'job_title': 'Software Developer', 'country': 'Serbia'} је дужине 4
После позива методе clear(): {}

Меморија

Пошто су колекције сложени типови, што значи да се при мењању не ствара нови објекат, није могуће направити копију простим изразом:

new_dict = original_dict

У овом случају и original_dict и new_dict ће показивати на исти објекат у меморији, односно њихова вредност ће бити референца.

У Python-у можемо распознати две врсте меморије које су нам битне за складиштење вредности: Stack и Heap. При стварању били простог (str, int, float..) било сложеног (dict, list, инстанца класе…) објекта ствара се нови објекат у Heap меморији док се у Stack меморији чува референца ка њему.

Разлика долази код мењања вредности. Ако је прости објекат у питању, при додељивању друге вредности прави се нови објекат на Heap меморији те се у Stack меморији референца мења на тај објекат. Код сложених типова се не мења референца, него се у Heap меморији мења вредност објекта. Пример:

person = {
    'first_name': 'Kosta',
    'last_name': 'Kupresak',
    'job_title': 'Software Developer',
    'address': 'Novi Sad',
    'country': 'Serbia',
    'blog': 'kostakuu.rs',
}
number = 4

# id функција враћа вредност референце ка објекту
ref_person = id(person)
ref_number = id(number)

print(f'Меморијска локација person: {ref_person}')
print(f'Меморијска локација number: {ref_number}')

print('-' * 10)

person['blog'] = 'https://kostakuu.rs'
number = 5

print(f'Меморијска локација после измене person: {id(person)}, стара локација {ref_person}')
print(f'Меморијска локација после измене number: {id(number)}, стара локација {ref_number}')

print('-' * 10)

person2 = person
number2 = number

person2['blog'] = 'Missing'
print(f'Меморијска локација person после измене person2: {id(person)}')
print(f'Меморијска локација person2 после измене person2: {id(person2)}')

print('-' * 10)

number2 = 6
print(f'Меморијска локација number после измене number2: {id(number)}')
print(f'Меморијска локација number2 после измене number2: {id(number2)}')

print('-' * 10)

person2 = person.copy()  # Стварање новог објекта, са новом референцом
print(f'Меморијска локација person после person2 = person.copy(): {id(person)}')
print(f'Меморијска локација person2 после person2 = person.copy(): {id(person2)}')

Испис:

Меморијска локација person: 27474272
Меморијска локација number: 1839908832
----------
Меморијска локација после измене person: 27474272, стара локација 27474272
Меморијска локација после измене number: 1839908848, стара локација 1839908832
----------
Меморијска локација person после измене person2: 27474272
Меморијска локација person2 после измене person2: 27474272
----------
Меморијска локација number после измене number2: 1839908848
Меморијска локација number2 после измене number2: 1839908864
----------
Меморијска локација person после person2 = person.copy(): 27474272
Меморијска локација person2 после person2 = person.copy(): 27474352

Када је реч о чишћењу меморије, то Python ради уместо нас уз помоћ механизма који се зове Garbage collector (GC). GC са времена на време скенира објекте те их уклања ако не постоји активна референца на њих.

Овиме завршавам данашњи чланак, у идућем настављам о колекцијама (list, tuple, set) и пролажењу кроз њих (for петља) .