将类变量作为默认值分配给类方法参数

Posted

技术标签:

【中文标题】将类变量作为默认值分配给类方法参数【英文标题】:assigning class variable as default value to class method argument 【发布时间】:2013-02-17 19:32:37 【问题描述】:

我想在一个类中使用取自该类的默认值参数构建一个方法。一般来说,我会过滤一些数据。在我的课堂上,我有一个通常传递数据向量的方法。有时我没有向量,我采用模拟数据。每次我不传递特定向量时,我都想默认采用模拟数据。我认为这应该是一个简单的构造,在我的方法定义中我说a=self.vector。但由于某种原因,我有一个错误NameError: name 'self' is not defined。简化的结构是:

class baseClass(object):  # This class takes an initial data or simulation
    def __init__(self):
        self.x = 1
        self.y = 2

class extendedClass(baseClass): # This class does some filtering
    def __init__(self):
        baseClass.__init__(self)
        self.z = 5
    def doSomething(self, a=self.z):
        self.z = 3
        self.b = a

if __name__ == '__main__':
    a = extendedClass()
    print a.__dict__
    a.doSomething()
    print a.__dict__

我期望的输出应该是:

'y': 2, 'x': 1, 'z': 5
'y': 2, 'x': 1, 'z': 3, 'b': 5

我尝试默认分配为def doSomething(self, a=z):,显然它永远不起作用。据我了解self.z 在此范围内可见,将其作为默认值应该不是问题。不知道为什么我有这个错误以及如何做到这一点。这可能是一个简单的问题,但我已经尝试弄清楚或找到不乏解决方案。我发现 similar 问题仅针对其他语言。

【问题讨论】:

【参考方案1】:

你的理解是错误的。 self 本身就是该函数定义的参数,因此此时它不可能在范围内。它只在函数本身的范围内。

答案是简单地将参数默认为None,然后在方法中检查它:

def doSomething(self, a=None):
    if a is None:
        a = self.z
    self.z = 3
    self.b = a

【讨论】:

如果参数不能取假值,你应该写if a: a = self.z甚至a = a or self.z 我想你的意思是if not a: a = self.z 这是一个标准的pythonic模式,即具有一个默认的类属性,用作可选方法属性的后备?或者将extendedClass init 构造函数更改为包含可选的z=None 属性是否更好,如果None 将其设置为方法内部的某个默认值,而不是将其作为方法参数?我只能认为拥有一个硬编码的类属性如果它真的是一个常量并且不止一种方法需要它是有用的,否则为什么不将z限制在方法的范围内,可能没有类或对象财产?【参考方案2】:

这是一个简单示例模块的反汇编代码。代码对象是字节码、它使用的常量和名称以及有关局部变量数量、所需堆栈大小等的元数据的只读容器。请注意,所有代码对象都被编译为常量。这些是在编译时创建的。但是对象class Afunction test 在执行时被实例化(例如,在导入模块时)。

要创建类,BUILD_CLASS 采用名称 'A'、基类 tuple (object,) 和包含类命名空间属性的 dict。这就像通过调用type(name, bases, dict) 手动实例化一个类型。为了创建dict,从代码对象A 创建一个函数并调用。最后,类对象通过STORE_NAME存储在模块命名空间中。

在代码对象A 中,self.z 作为MAKE_FUNCTION 的参数加载到堆栈中。字节码操作 @​​987654336@ 将在当前本地(即正在定义的类名称空间)、模块全局变量和内置函数中搜索 self。如果self 没有在全局或内置范围中定义,这显然会失败;它显然没有在本地范围内定义。

但是,如果它成功了,那么将使用(self.z,) 作为其__defaults__ 属性创建该函数,然后将其存储到本地名称test

>>> code = compile('''
... class A(object):
...   def test(self, a=self.z): pass
... ''', '<input>', 'exec')

>>> dis.dis(code)
  2           0 LOAD_CONST               0 ('A')
              3 LOAD_NAME                0 (object)
              6 BUILD_TUPLE              1
              9 LOAD_CONST               1 (<code object A ...>)
             12 MAKE_FUNCTION            0
             15 CALL_FUNCTION            0
             18 BUILD_CLASS         
             19 STORE_NAME               1 (A)
             22 LOAD_CONST               2 (None)
             25 RETURN_VALUE

>>> dis.dis(code.co_consts[1]) # code object A
  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  3           6 LOAD_NAME                2 (self)
              9 LOAD_ATTR                3 (z)
             12 LOAD_CONST               0 (<code object test ...>)
             15 MAKE_FUNCTION            1
             18 STORE_NAME               4 (test)
             21 LOAD_LOCALS         
             22 RETURN_VALUE       

@uselpa: 您的 pastebin 示例(为 2.x 重写):

>>> code = compile('''
... default = 1
... class Cl(object):
...     def __init__(self, a=default):
...         print a
... Cl()
... default = 2
... Cl()
... ''', '<input>', 'exec')
>>> dis.dis(code)
  2           0 LOAD_CONST               0 (1)
              3 STORE_NAME               0 (default)

  3           6 LOAD_CONST               1 ('Cl')
              9 LOAD_NAME                1 (object)
             12 BUILD_TUPLE              1
             15 LOAD_CONST               2 (<code object Cl ...>)
             18 MAKE_FUNCTION            0
             21 CALL_FUNCTION            0
             24 BUILD_CLASS         
             25 STORE_NAME               2 (Cl)

  6          28 LOAD_NAME                2 (Cl)
             31 CALL_FUNCTION            0
             34 POP_TOP             

  7          35 LOAD_CONST               3 (2)
             38 STORE_NAME               0 (default)

  8          41 LOAD_NAME                2 (Cl)
             44 CALL_FUNCTION            0
             47 POP_TOP             
             48 LOAD_CONST               4 (None)
             51 RETURN_VALUE        

如您所见,类对象Cl(和函数对象__init__)仅被实例化并存储到本地名称'Cl' 一次。该模块在运行时按顺序执行,因此随后重新绑定名称 default 将不会影响 __init__ 中的默认值。

您可以使用先前编译的代码和新的默认值动态实例化一个新函数:

>>> default = 1
>>> class Cl(object):
...     def __init__(self, a=default):
...         print a
... 

>>> from types import FunctionType
>>> default = 2
>>> Cl.__init__ = FunctionType(
...   Cl.__init__.__code__, globals(), '__init__', (default,), None)
>>> c = Cl()
2

这会重用来自__init__.__code__ 的已编译代码对象来创建一个带有新的__defaults__ 元组的函数:

>>> Cl.__init__.__defaults__
(2,)

【讨论】:

我卡住了。如果类和方法是在实例化时创建的,那么为什么它们显然不在我的 pastebin.com/UJfMpnqW 示例中? @uselpa:我添加了您的示例以希望阐明“编译时间”和“运行时间”之间的区别。模块可以正常编译而没有语法错误,但在导入(执行)时仍然会出现运行时错误。 我想我明白了。非常感谢您的耐心等待!【参考方案3】:

默认参数仅在定义执行时被评估一次。而是这样做:

def doSomething(self, a=None):
    if a is None:
        a = self.z
    self.z = 3
    self.b = a

另见http://docs.python.org/release/3.3.0/tutorial/controlflow.html#more-on-defining-functions。

【讨论】:

【参考方案4】:

如果aNone/False/empty_value,这将插入self.z

def doSomething(self, a=None):
        self.z = 3
        self.b = (a or self.z)

【讨论】:

以上是关于将类变量作为默认值分配给类方法参数的主要内容,如果未能解决你的问题,请参考以下文章

面向对象 --- 类的绑定方法,面向对象高阶

如何将另一个文件中的变量分配给类常量?

PHP SET 函数中的默认参数作为静态变量

009---绑定方法与非绑定方法

如何在没有参数的情况下快速将类保存到用户默认值

如何在 Python 中使用类变量作为默认参数值