Python - 传递对象值而不是引用

时间:2016-11-17 20:58:47

标签: python python-2.7 reference

在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

4 个答案:

答案 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做同样的事情。