在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
时传递噪音的值?我想我可以通过使用函数来隔离命名空间,这确实有效:
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
答案 0 :(得分:2)
只需将噪音值传递给setattr
函数即可。 E.g。
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'
答案 1 :(得分:1)
这种情况发生了,因为在Python函数(包括lambda
s)中使用了符号绑定,即你的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 :(得分: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
答案 3 :(得分: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
做同样的事情。