4

我想做类似以下的事情:

class Foo(object):
    def __init__(self):
        self.member = 10
        pass

def factory(foo):
    foo = Foo()

aTestFoo = None
factory(aTestFoo)

print aTestFoo.member

但是它崩溃了AttributeError: 'NoneType' object has no attribute 'member':对象aTestFoo没有在函数调用中被修改factory

执行此操作的 pythonic 方式是什么?这是要避免的模式吗?如果是当前的错误,它是如何命名的?

在 C++ 中,在函数原型中,我会添加对要在工厂中创建的指针的引用……但也许这不是我在 Python 中应该考虑的事情。

在 C# 中,有一个关键字ref允许修改引用本身,非常接近 C++ 方式。我不知道 Java ......我确实想知道 Python。

4

4 回答 4

12

Python 没有通过引用传递。顺便说一句,它与 Java 共享的少数东西之一。有些人将 Python 中的参数传递描述为按值调用(并将值定义为引用,其中引用的意思不是它在 C++ 中的含义),有些人将其描述为按引用传递,我觉得这很值得怀疑(他们重新定义了它使用 Python 所谓的“引用”,并最终得到与几十年来被称为通过引用无关的东西),其他人则使用没有被广泛使用和滥用的术语(流行的例子是“ {对象,共享}的{传递,调用})。请参阅按对象调用在 effbot.org 上对各种术语的定义、历史以及这些术语的某些论点中的缺陷进行了相当广泛的讨论。

没有命名的短篇故事是这样的:

  • 每个变量、对象属性、集合项等都指向一个对象。
  • 赋值、参数传递等创建另一个变量、对象属性、集合项等,该变量引用同一个对象,但不知道哪些其他变量、对象属性、集合项等引用了该对象。
  • 任何变量、对象属性、集合项等都可以用于修改对象,并且任何其他变量、对象属性、集合项等都可以用于观察该修改。
  • 没有变量、对象属性、集合项等引用另一个变量、对象属性、集合项等,因此您不能模拟按引用传递(在 C++ 意义上),除非将可变对象/集合视为您的“命名空间”。这太难看了,所以当有更简单的选择时不要使用它(例如返回值,或异常,或通过可迭代解包的多个返回值)。

您可能会认为这就像在 C 中使用指针,但不是指向指针的指针(但有时是指向包含指针的结构的指针)。然后按值传递这些指针。但不要过多解读这个比喻。Python 的数据模型与 C 的数据模型有很大不同。

于 2012-10-09T10:21:57.240 回答
10

你在这里犯了一个错误,因为在 Python

"We call the argument passing technique _call by sharing_,
because the argument objects are shared between the
caller and the called routine.  This technique does not
correspond to most traditional argument passing techniques
(it is similar to argument passing in LISP).  In particular it
is not call by value because mutations of arguments per-
formed by the called routine will be visible to the caller.
And it is not call by reference because access is not given
to the variables of the caller, but merely to certain objects."

在 Python 中,形式参数列表中的变量绑定到实际参数对象。对象在调用者和被调用者之间共享;不涉及“新鲜地点”或额外的“商店”。

(当然,这就是为什么 CLU 的人称这种机制为“共享调用”的原因。)

顺便说一句,Python 函数也不能在扩展环境中运行。功能体对周围环境的访问非常有限。

于 2012-10-09T10:25:42.997 回答
3

Python 文档的赋值语句部分可能很有趣。

Python 中的=语句会根据情况采取不同的方式,但在您呈现的情况下,它只是将新对象绑定到新的局部变量:

def factory(foo):
    # This makes a new instance of Foo,
    # and binds it to a local variable `foo`,
    foo = Foo()

# This binds `None` to a top-level variable `aTestFoo`
aTestFoo = None

# Call `factory` with first argument of `None`
factory(aTestFoo)

print aTestFoo.member

尽管它可能更令人困惑而不是有用,但该dis模块可以向您展示函数的字节码表示,这可以揭示 Python 内部的工作方式。这是`工厂的反汇编:

>>> dis.dis(factory)
  4           0 LOAD_GLOBAL              0 (Foo)
              3 CALL_FUNCTION            0
              6 STORE_FAST               0 (foo)
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        

也就是说,PythonFoo按名称 (0) 加载全局类,并调用它(3,实例化和调用非常相似),然后将结果存储在局部变量中(6,参见STORE_FAST)。然后它加载默认返回值None(9)并返回它(12)

执行此操作的 pythonic 方式是什么?这是要避免的模式吗?如果是当前的错误,它是如何命名的?

Python 中很少需要工厂函数。在偶尔需要它们的情况下,您只需从工厂返回新实例(而不是尝试将其分配给传入的变量):

class Foo(object):
    def __init__(self):
        self.member = 10
        pass

def factory():
    return Foo()

aTestFoo = factory()

print aTestFoo.member
于 2012-10-09T10:36:28.073 回答
1

您的工厂方法不返回任何内容 - 默认情况下,它的返回值为None. 您分配aTestFooNone,但从不重新分配它 - 这是您实际错误的来源。

解决这些问题:

class Foo(object):
    def __init__(self):
        self.member = 10
        pass

def factory(obj):
    return obj()

aTestFoo = factory(Foo)
print aTestFoo.member

这应该做我认为你所追求的,尽管这种模式在 Python 中并不典型(即工厂方法)。

于 2012-10-09T10:22:31.287 回答