使用当前类实例的属性作为方法参数的默认值

61
虽然在Python中可以为函数参数设置默认值:

可能是重复的问题:
default value of parameter as result of instance method

def my_function(param_one='default')
    ...

似乎无法访问当前实例(self):

class MyClass(..):

    def my_function(self, param_one=self.one_of_the_vars):
        ...

我的问题:

  • 在函数中,我不能访问当前实例以设置默认参数,这是真的吗?
  • 如果不可能的话:原因是什么?未来版本的Python是否可以实现这一点?

这有点不同,Ashwini。那篇文章是在问实例方法,而这篇文章是关于实例属性的。两者都存在问题的原因是相同的,但问题肯定是不同的。 - Alex V
@Maxwell 但是有点相同,因为他试图在默认参数值中使用 self.someattribute - Ashwini Chaudhary
1
@Ashwini 确实如此,但这并不是将其作为重复项关闭的理由,因为要求是它是涵盖完全相同内容精确副本,并且可以将此问题的答案与其他问题的答案合并。在“可以合并”的表述中,隐含着这些合并的问题不应该因为它们不能回答问题而显得格格不入。 - Lauritz V. Thaulow
@lazyr 没有投票,是其他人投的票。我只是添加了评论。 - Ashwini Chaudhary
1
重要的一点是默认参数在 定义时 设置,但是 self 直到 运行时 才存在。 - Katriel
4个回答

62

它被写成:

def my_function(self, param_one=None): # Or custom sentinel if None is vaild
    if param_one is None:
        param_one = self.one_of_the_vars

我认为可以放心地说,在Python中这是不可能发生的,因为self在函数开始之前并不存在...(你不能在其自身的定义中引用它,就像其他所有东西一样)

例如:你不能这样做:d = {'x': 3, 'y': d['x'] * 5}


2
这是一个好答案。我认为更有启发性的方法是编写一个函数:def my_func(a, foo=a.bar): ...并展示该函数因为a是函数的参数,但在定义函数时就确定了foo的默认值而导致无法正常工作。看到self实际上没有什么特别之处其实很不错。它只是函数调用中的另一个参数--和其他一切一样-- (只是当您执行instance.method()时隐式传递一个参数)。 - mgilson
1
仅为完整起见:如果您的 one_of_the_vars 恰好是在同一类中定义的常量属性,那么您可以通过 def my_function(self, param_one=ONE_OF_THE_CONSTANTS) 在方法中简单地引用它。这将起作用。 - RayLuo

24

这远比你想象的要复杂得多。将默认值视为 静态 (指向一个对象的常量引用),并存储在定义的某个位置;在方法定义时计算;作为而非实例的一部分。因为它们是常量,所以不能依赖于 self

下面是一个例子。虽然有点反直觉,但实际上是完全合理的:

def add(item, s=[]):
    s.append(item)
    print len(s)

add(1)     # 1
add(1)     # 2
add(1, []) # 1
add(1, []) # 1
add(1)     # 3

这将打印出1 2 1 1 3

因为它的工作方式与

default_s=[]
def add(item, s=default_s):
    s.append(item)

显然,如果您修改了default_s,它将保留这些修改。

有各种解决方法,包括

def add(item, s=None):
    if not s: s = []
    s.append(item)

或者你可以这样做:

def add(self, item, s=None):
    if not s: s = self.makeDefaultS()
    s.append(item)

那么方法makeDefaultS将能够访问self

另一个变化:

import types
def add(item, s=lambda self:[]):
    if isinstance(s, types.FunctionType): s = s("example")
    s.append(item)

这里s的默认值是一个工厂函数

你可以结合使用所有这些技巧:

class Foo:
    import types
    def add(self, item, s=Foo.defaultFactory):
        if isinstance(s, types.FunctionType): s = s(self)
        s.append(item)

    def defaultFactory(self):
        """ Can be overridden in a subclass, too!"""
        return []

1
我不确定在这里使用“常量”一词是否合适,因为您可以改变对象。但是指出默认值与关键字相关联是在函数定义时很有帮助的。 - mgilson
谢谢,是的,我的意思是静态的 - Has QUIT--Anony-Mousse
1
@mgilson 这是一个常量对象引用;如果该对象是可变的,则可以进行更改,但是引用本身不能更改。尽管“常量”一词可能会让人感到困惑,但如果您将其视为引用,则很适合。 - l4mpi
我还添加了一些解决方案以获得所需的行为:工厂函数,或将默认值初始化委托给单独的方法。 - Has QUIT--Anony-Mousse
1
@l4mpi -- 你对于它是一个常量引用是完全正确的,但我认为这个术语对于可能会问这样问题的人仍然很困惑。 - mgilson

3

参数的默认值在编译时被评估一次。因此,显然您无法访问self。经典示例是将list作为默认参数。如果向其中添加元素,则参数的默认值会更改!

解决方法是使用另一个默认参数,通常是None,然后检查并更新变量。


使用列表作为默认参数值可能会导致“最少惊讶原则”。 - Ashwini Chaudhary
我不确定你想要表达什么......它遵循了规则,但如果你不知道这个规则,那肯定是一个大惊喜。 - Karoly Horvath

2

您在这里做出了多个错误的假设——首先,函数属于类而不是实例,这意味着任何两个类的实例所涉及的实际函数是相同的。其次,默认参数在编译时计算并且是常量(即常量对象引用——如果参数是可变对象,则可以更改它)。因此,您不能在默认参数中访问 self ,也永远无法访问。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接