2016-11-17 22 views
3

在Python中,據我瞭解,變量實際上是對給定名稱空間中對象的引用。因此,在下面的示例中,當全局名稱空間中的noise更改時,由cat.noise返回的值會發生變化,因爲setattr行中的引用使用的是noise引用,而不是其基礎值。Python - 傳遞對象值而不是引用

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當傳遞值噪聲的?我想,我可以通過使用功能隔離的命名空間,並做了工作:

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等)?作爲第二個問題,我的推理是否正確?

EDIT

按照用於評價一個較少人爲的例子的要求,考慮以下。想象一下,我試圖在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 
+1

你爲什麼要使用一個屬性,而不是直接的價值本身? – Evert

+0

@Evert不幸的是,在我正在使用的現實世界中,它需要屬性,因爲lambda函數更復雜。我知道上面的例子有點人爲... – aensm

+0

「它確實需要屬性,因爲lambda函數更復雜」 - 完全是什麼問題?你可以舉一個不太人爲的例子嗎? –

回答

1

出現這種情況,因爲在Python功能(包括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。

2

噪音值僅僅傳遞給setattr功能。例如。

class C(object): 
    pass 


noise = 'mrrrr' 

setattr(C, 'noise', noise) 

c = C() 

c.noise 
# outputs 'mrrrr' 

noise = 'qwe' 

c.noise 
# outputs 'mrrrr' 

編輯:對於情況下,需要某種原因getter函數。

您可以使用中間值。

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' 
+0

不幸的是,這不是一個選項。這個例子是有點人爲的,但在我正在處理的現實世界中,它需要成爲一個屬性。 – aensm

+0

增加了另一段代碼,它使用中間值和屬性函數。 – sardok

0

隨着

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可變(如列表)

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做同樣的事情。

1

Python函數(包括lambda函數)通過​​名稱引用非本地變量。這意味着他們將獲得該變量的最新值,而不是它在函數定義時的最新值(事實上,函數定義時變量不需要定義)。

解決此問題的一種方法是將該值作爲函數參數的默認值。默認值是在函數定義時計算的,而不是函數被調用的時候(這就是爲什麼可變默認參數經常會出問題的原因)。

嘗試這樣:

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 
相關問題