Коллекция Python ссылок на неизменяемые

Есть ли способ в python получить список ссылок? То есть набор вещей, который автоматически меняется, когда я меняю одну из внутренних вещей, без необходимости обращаться к самой коллекции.

Что-то, что позволило бы то, что я показываю в своем примере

# What happens
a = 1
b = 2
L = [a, b]
print(L) # [1, 2]

b = 3
print(L) # [1, 2]


# I would like to have something like
a = 1
b = 2
ra = ref(a)
rb = ref(b)
L = [ra, rb]
print(L) # [1, 2]

b = 3
print(L) # [1, 3]

EDIT Мой первый пример был бы работать, если бы числа были изменяемыми в python. т.е. если переназначение (например: b = 3) будет означать «изменить память объекта, на который мы ссылаемся, через метку «b»».

Мой первый пример работает с изменяемыми элементами списка (например, если я создаю список списков и изменяю - без переназначения! - внутренние списки).

Поэтому, чтобы быть более конкретным, мой вопрос: есть ли у нас шанс иметь такое поведение, когда коллекция содержит неизменяемые элементы?

(В частности меня интересуют списки callables).


person Michele Piccolini    schedule 13.09.2019    source источник
comment
Вы в основном ответили на свой вопрос - Python уже ссылается на каждый объект по их ссылке. Проблема заключается в том, что вы переназначаете ссылку на неизменяемый объект или просто не используете доступные методы для изменяемого объекта. Когда вы имеете дело с коллекцией неизменяемых элементов, вы должны работать непосредственно с самой коллекцией.   -  person r.ook    schedule 13.09.2019
comment
Это также может вас заинтересовать: stackoverflow.com/questions/44935204/   -  person r.ook    schedule 13.09.2019


Ответы (2)


Прежде чем печатать новое значение L, снова сохраните значение b в L, пожалуйста, найдите следующий код:

a = 1
b = 2
L = [a, b]
print(L) # [1, 2]
b = 3
L=[a, b]
print(L) # [1, 3]

Но этот метод не эффективен. Способ обновления элемента списка - только по индексу. Как показано ниже:

a = 1
b = 2
L = [a, b]
print(L) # [1, 2]
b = 3
L[1]=b
print(L) # [1, 3]

Мы не можем сделать это так, как вы пытались сделать, используя ссылки в Python. Когда вы помещаете целое число в список, список содержит копию целого числа. Неважно, было ли целое число изначально переменной, литеральным значением, результатом вызова функции или чем-то еще; к тому времени, когда список увидит его, это будет просто целочисленное значение.

person Bharat Gera    schedule 13.09.2019
comment
Мое требование состоит в том, что я хочу изменить то, что находится внутри L, никогда не обращаясь к L снова. В этом смысл наличия набора ссылок, так что если у меня есть, скажем, L[a, b, a, a, a, c, a, d], я могу просто сказать, замени a, и все a в L будут иметь измененный. Кроме того, я отредактировал свой вопрос, чтобы сделать его более конкретным, поскольку речь идет только о коллекциях неизменяемых. - person Michele Piccolini; 13.09.2019
comment
[a,b,a,a,b,c] - это создаст список, который является изменяемым. Целые числа неизменны по своей природе. Когда вы выполняете итерацию по этому списку, вы можете изменить содержимое списка, потому что список изменяем, однако сами целые числа не меняются, поскольку они неизменяемы. Что Python делает здесь (за кулисами), так это создает новый целочисленный объект со значением 1 и переключает указатель на этот новый объект. - person Bharat Gera; 13.09.2019
comment
Спасибо. Как я сказал в своем предыдущем комментарии, я ищу обходной путь для случаев, когда у меня нет доступа к ссылке на коллекцию неизменяемых элементов, но я все же хочу ее изменить. Или, даже если бы он у меня был, я бы хотел более эргономичный способ изменить все значения, которые зависят от какой-то переменной (например, которая может появляться в коллекции несколько раз). Смотрите мой ответ, чтобы узнать, как реализовать этот обходной путь. - person Michele Piccolini; 13.09.2019

Ок, нашел способ. Я просто оборачиваю неизменяемые объекты изменяемой оболочкой.

Например, если мы хотим иметь изменяемые целые числа:

class mut_int:
    def __init__(self, data):
        self.data = data

    def mutate(self, new_data):
        self.data = new_data

    def __repr__(self):
        return repr(self.data)

a = mut_int(1)
L = [a, a]
print(L) # [1, 1]

# from now on, we assume we don't have access to L anymore

a.mutate(2) # simulating the mutable a = 2
print(L) # [2, 2] # we changed L without accessing it directly, nice!

Аналогично для изменяемых вызываемых объектов:

class mut_callable:
    def __init__(self, func):
        self.func = func

    def mutate(self, new_func):
        self.func = new_func

    def __repr__(self):
        return repr(self.func)

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

f = mut_callable(lambda x: x)
L = [f, lambda x: f(x) + f(x), lambda x: f(f(x))]
print([g(2) for g in L])
# Out: [2, 4, 2] 
# i.e. [identity(2), 2+2, identity(identity(2))]

# from now on, we assume we don't have access to L anymore

f.mutate(lambda x: x**2)
print([g(2) for g in L])
# Out: [4, 8, 16]
# i.e. [square(2), 2+2, square(square(2))]

К сожалению, это немного хрупко из-за динамической природы python и отсутствия возможности перегрузки оператора присваивания (a = 3). Как только вы используете обычное присваивание для изменяемой оболочки, оболочка просто потеряется, и мы вернемся к исходной точке.

a = mut_int(1)
L = [a, a]
print(L) # [1, 1]
a.mutate(2)
print(L) # [2, 2] good so far

a = 3 # !ACHTUNG! We did not stick to the api, and a is no more a mut_int!
print(L) # still [2, 2] :(
a.mutate(4) # AttributeError

Если кто-то найдет более элегантное и/или эргономичное и/или надежное решение, я все еще жду его!

person Michele Piccolini    schedule 13.09.2019