Страницы

Поиск по вопросам

пятница, 27 декабря 2019 г.

Переменные-ссылки в Python

#python #указатели #ссылки


Я так понял, что все переменные в Python - это ссылки, которые ссылаются на один
объект, а не на участок памяти как в С/C++. 

А есть ли возможность создания ссылки на один участок памяти, чтобы изменялось значение
при работе с любой из ссылок?

Например (условно),

a = 10
b = &a
print(a, b)  # 10 10
b = 20
print(a, b)  # 20 20
a = 15
print(a, b)  # 15 15


Мне это нужно, чтобы сокращать запись для доступа ко внутренним значениям вложенных
словарей:

username   = "test"
userdata   = [10, 2, 30, 15]
user       = {username:userdata}

serveraddr = "127.0.0.1"
server     = {serveraddr:user}

# хочется создать имена-сокращения для этих элементов    
joincount  = server[serveraddr][username][0]
leftcount  = server[serveraddr][username][1]
gamescount = server[serveraddr][username][2]
wincount   = server[serveraddr][username][3]

# пробуем изменить их значения
joincount  = 20
leftcount  = 9
gamescount = 10
wincount   = 3

# проверяем, что значения в словаре остались прежними
print(server[serveraddr][username][0], server[serveraddr][username][1],
      server[serveraddr][username][2], server[serveraddr][username][3])
# (10, 2, 30, 15)

# хотя значения переменных изменились
print joincount, leftcount, gamescount, wincount
# (20, 9, 10, 3)


В коде очень часто используются конструкции вида server[serveraddr][username][n],
большая их часть приходится на места где все индексы вычислены. Поэтому хотелось бы
вместо server[serveraddr][username][0] использовать joincount - это улучшило бы читаемость
кода.

Возможно ли это реализовать?
    


Ответы

Ответ 1



В Python есть изменяемые и неизменяемые типы. Изменяемые отличаются тем, что их содержимое можно сменить, не изменив ссылку на них. Неизменяемые объекты приходится пересоздавать, чтобы отразить изменения состояния. При этом все старые ссылки не видят это обновление, потому что указывают на старый объект. Поясню на практике. Списки, словари, множества - это изменяемые объекты: l1 = [1, 2, 3] l2 = l1 print(l1, l2, id(l1), id(l2)) # [1, 2, 3] [1, 2, 3] 139917408901064 139917408901064 l1[1] = 10 print(l1, l2, id(l1), id(l2)) # [1, 10, 3] [1, 10, 3] 139917408901064 139917408901064 Числа, строки, кортежи - это неизменяемые объекты: v1 = 1024 v2 = v1 print(v1, v2, id(v1), id(v2)) # 1024 1024 ...7040 ...7040 v1 = 2048 print(v1, v2, id(v1), id(v2)) # 2048 1024 ...5312 ...7040 t1 = (1, 2, 3) t2 = t1 print(t1, t2, id(t1), id(t2)) # (1, 2, 3) (1, 2, 3) ...6232 ...6232 # t1[1] = 10 # не сработает, так как кортежи неизменяемые t1 = (1, 10, 3) print(t1, t2, id(t1), id(t2)) # (1, 10, 3) (1, 2, 3) ...7240 ...6232 В своём коде вы используете изменяемые структуры данных, такие как словари и списки (user, userdata, server) и неизменяемые строки и числа. Таким образом, ничего не изменяя в структуре хранения информации, максимально можно сократить только доступ до списка чисел my_userdata = server[serveraddr][username] # изменение my_userdata влечёт изменение server[serveraddr][username], # так как это один и тот же объект Так как числа неизменяемые, то сохранение их в переменную и дальнейшее изменение этой переменной никак не сказываются на исходном числе (т.е. переменная joincount никогда не изменится при изменении server[serveraddr][username][0], и наоборот). изменение чисел в этом списке никак не отразится на переменных, хранящих эти значения Но если попробовать немного улучшить код, то стоит создать класс UserData, который будет хранить в себе нужные значения и позволять их изменять. Минимальный пример будет следующий: class UserData: def __init__(self, joincount, leftcount, gamescount, wincount): self.joincount = joincount self.leftcount = leftcount self.gamescount = gamescount self.wincount = wincount username = "test" userdata = UserData(10, 2, 30, 15) user = {username:userdata} После этого, если мы получаем откуда-то нашего пользователя, мы можем изменить нужные значения: user_data = server[serveraddr][username] user_data.joincount = 20 user_data.leftcount = 9 user_data.gamescount = 10 user_data.wincount = 3 print (server[serveraddr][username].joincount, server[serveraddr][username].leftcount, server[serveraddr][username].gamescount, server[serveraddr][username].wincount) print (user_data.joincount, user_data.leftcount, user_data.gamescount, user_data.wincount) Использование подобной структуры увеличит читаемость вашего кода, по сравнению с использованием индексов списка. Если есть доступ на изменение содержимого словаря, я бы рекомендовал вам вместо самописного класса использовать namedtuple из стандартного модуля collections, который позволяет создавать объект, по поведению схожий с кортежем, но предоставляющий доступ к полям, как у класса выше (но только на чтение): UserData = collections.namedtuple('UserData', ['joincount', 'leftcount', 'gamescount', 'wincount']) В этом случае придётся заменять старый объект UserData новым: old_userdata = server[serveraddr][username] new_userdata = UserData(joincount=20, leftcount=9, gamescount=10, wincount=3) server[serveraddr][username] = new_userdata print(new_userdata, old_userdata, server[serveraddr][username]) Получение поля на чтение будет выполняться так же, как и в примере выше: print(old_userdata.joincount, old_userdata.gamescount)

Ответ 2



Вам нужно прочитать и разобраться с mutable и immutable объектами в python. Да вы можете делать ссылку на вложенный список, и заполнять его через нее. Например: lst = [[]] sublst = lst[0] sublst.append(1) print(lst) sublst.append(2) print(lst) print(id(lst[0])) print(id(sublst)) sublst[0] = -1 print(lst) Вывод: [[1]] [[1, 2]] 139794716732424 139794716732424 [[-1, 2]] В данном случае sublst это ссылка на вложенный список в lst, вызов sublst.append равнозначен lst[0].append. Аналогично вы можете работать например со словарем.

Комментариев нет:

Отправить комментарий