Python – передача значения объекта вместо ссылки

В Python, как я понимаю, переменные являются действительно ссылками на объекты в данном пространстве имен. Поэтому в следующем примере неудивительно, что при изменении noise в глобальном пространстве имен значение, возвращаемое cat.noise изменяется, поскольку ссылка в строке setattr использует ссылку на noise , а не ее базовое значение.

 class Cat(object): pass noise = "meow" setattr(Cat, "noise", property(lambda self: noise)) cat = Cat() cat.noise # Outputs "meow" noise = "purrrrr" cat.noise # Outputs "purrrrr" 

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

 class Cat(object): pass noise = "meow" def setproperties(cls, k, v): setattr(cls, k, property(lambda self: v)) setproperties(Cat, "noise", noise) cat = Cat() cat.noise # Outputs "meow" noise = "purrrrr" cat.noise # Still outputs "meow" 

Возможно ли это сделать, не передавая объект через функцию (без использования eval или тому подобного)? И как второстепенный вопрос, правильно ли я рассуждаю о том, что происходит под капотом?

РЕДАКТИРОВАТЬ

В соответствии с просьбой о менее надуманном примере в комментариях рассмотрите следующее. Представьте себе, что я пытаюсь динамически устанавливать атрибуты в Cat , основываясь на значениях его друга. Dog :

 class Dog(object): noise = 'woof' adorable = True class Cat(object): friend = Dog friend_attrs = filter(lambda attr: not attr.startswith('__'), Dog.__dict__) for attr in friend_attrs: setattr(Cat, "friend_" + attr, property(lambda self: getattr(self.friend, attr))) cat = Cat() cat.friend_noise # Outputs True cat.friend_adorable # Outputs True 

Это происходит, потому что в функциях Python (включая lambda ) используется символическая привязка, т. lambda Ваши lambda точки в переменной, а не ее значение. Чтобы преодолеть это, вы должны заключить эту переменную (создать закрытие):

 noise = "meow" f1 = lambda self: noise f2 = (lambda x: (lambda self: x))(noise) noise = "mur" print(f1(None)) # -> "mur" print(f2(None)) # -> "meow" 

Но вы уже нашли это, используя функцию, чтобы заключить операцию. Это Pythonic.

Просто передайте значение шума в функцию setattr . Например

 class C(object): pass noise = 'mrrrr' setattr(C, 'noise', noise) c = C() c.noise # outputs 'mrrrr' noise = 'qwe' c.noise # outputs 'mrrrr' 

Изменить : для случая, когда функция геттера необходима по какой-либо причине.

Вы можете использовать промежуточное значение.

 class D(object): pass noise = 'mrrrr' setattr(D, '_noise', noise) setattr(D, 'noise', property(lambda self: self._noise)) d = D() d.noise # Outputs 'mrrrr' noise = 'pheew' d.noise # Outputs 'mrrrr' 

Редактирование 2 : использование частичных функций.

 import functools class E(object): pass noise = 'mrrrr' getter = lambda _, noise: noise partial = functools.partial(getter, noise=noise) setattr(E, 'noise', property(partial)) e = E() e.noise # Outputs 'mrrrr' noise = 'fooooo' e.noise # Outputs 'mrrrr' 

Функции Python (включая лямбда-функции) относятся к нелокальным переменным по имени. Это означает, что они получат последнее значение этой переменной, а не ту, которая была у нее при определении функции (действительно, переменная не обязательно должна была быть определена вообще при определении функции).

Один из способов обойти это – поставить значение в качестве значения по умолчанию для аргумента функции. Значение по умолчанию оценивается во время определения функции, а не при вызове функции (поэтому часто изменяемые аргументы по умолчанию часто возникают).

Попробуйте что-то вроде этого:

 for attr in friend_attrs: setattr(Cat, "friend_" + attr, property(lambda self, attr=attr: # added attr=attr default here! getattr(self.friend, attr))) # attr is the lambda's arg here 

С

 setattr(Cat, "noise", property(lambda self: noise)) 

просто определяет свойство как функцию, которая возвращает значение глобальной noise переменной. Это похоже на

 def make_noise(self): return noise 

Второй случай немного сложнее. Свойство теперь возвращает значение v , которое было определено во время создания.

Я могу произвести такое же поведение с помощью простой функции:

 In [268]: bar = 'barstring' In [269]: def foo(): ...: return bar # returns the global ...: In [270]: foo() Out[270]: 'barstring' In [271]: bar = 'xxx' In [272]: foo() Out[272]: 'xxx' 

но если я передаю bar как ключевое слово, я могу заблокировать значение, до текущего:

 In [273]: def foo1(bar=bar): ...: return bar ...: In [274]: foo1() Out[274]: 'xxx' In [275]: bar = 'ooo' In [276]: foo1() Out[276]: 'xxx' In [277]: foo() Out[277]: 'ooo' 

Но если я сделаю bar mutable (например, список)

 In [278]: bar=['one'] In [279]: foo() Out[279]: ['one'] In [280]: def foo1(bar=bar): ...: return bar ...: In [281]: foo1() Out[281]: ['one'] In [282]: bar[0]='two' In [283]: foo1() Out[283]: ['two'] 

foo1 возвращает элемент из его __defaults__ (если я не даю ему аргумента). foo продолжает получать доступ к глобальному, потому что нет локальной привязки для bar .

 In [289]: foo1.__defaults__ Out[289]: (['two'],) In [290]: foo.__defaults__ 

В глубине души речь идет о том, где переменная связана – локально, в некоторой функции контейнера или во всем мире.

Могут использоваться взаимозаменяемые значения по умолчанию, подобные этому, но они также могут быть причиной ошибок.

============

Другой пример локальной привязки

 In [297]: def make_foo(bar): ...: def foo(): ...: print(locals()) ...: return bar ...: return foo ...: In [298]: foo2=make_foo('test') In [299]: foo2() {'bar': 'test'} Out[299]: 'test' 

foo выглядит одинаково, но теперь bar ссылается на переменную, связанную с ее locals . partial и ваши setproperties делают то же самое.