函数参数名称中的类与嵌套的类属性冲突

时间:2019-02-19 16:39:02

标签: python scope closures inner-classes

考虑以下代码:

def gee(bool_, int32, int64, str_):

    class S:
        bool_ = bool_
        int32 = int32
        int64 = int64
        str_ = str_

    return S

gee(1, 2, 3, 4)

运行此操作会出现错误:

Traceback (most recent call last):
  File "test_.py", line 36, in <module>
    gee(1, 2, 3, 4)
  File "test_.py", line 27, in gee
    class S:
  File "test_.py", line 28, in S
    bool_ = bool_
NameError: name 'bool_' is not defined

我不知道在这里适用哪些范围/关闭规则。 nonlocal修复了该错误,但结果并非我所期望的:

def gee(bool_, int32, int64, str_):

    class S:
        nonlocal bool_, int32, int64, str_
        bool_ = None
        int32 = None
        int64 = None
        str_ = None
    print(bool_, int32, int64, str_ )

    return S

g = gee(1, 2, 3, 4)
g.bool_

输出:

None None None None
Traceback (most recent call last):
  File "test_.py", line 38, in <module>
    g.bool_
AttributeError: type object 'S' has no attribute 'bool_'

除了重命名外,我还能做些什么来使第一段代码起作用?为什么会这样呢?因为有name = ...吗?为什么Python在分配之前不评估名称?

6 个答案:

答案 0 :(得分:2)

在范围内,如果您分配给名称,则该名称在该范围内是本地的。类主体是作用域。因此,_bool是本地的,因此尝试为其分配_bool是错误的,因为尚未定义_bool。与其他一些语言不同,当尚未在作用域中定义名称时,Python不会在外部作用域中查找。

您注意到

nonlocal不能解决问题,因为它会导致将外部作用域的名称分配给它。

简单的答案是使用不同的名称。

如果仅在S的方法中使用这些值,另一种可能性是简单地使用闭包并在方法中访问值,就好像它们是本地值一样。然后,您根本就不需要它们作为类的属性。

答案 1 :(得分:2)

编译类时,将使用与功能相似的规则来解析名称。因为类主体包含对bool_变量的赋值,所以解释器将其视为类变量,因此当被要求在赋值的右侧解析其值时,它会在类名称空间中查找。

一种可能性是使它们成为在__init__方法中分配的实例变量,从而将参数默认为对gee的调用中提供的值:

def gee(bool_, int32, int64, str_):

    class S:
        def __init__(self, bool_=bool_, int32=int32, int64=int64, str_=str_):
            self.bool_ = bool_
            self.int32 = int32
            self.int64 = int64
            self.str_ = str_

    return S

my_class = gee(1, 2, 3, 4)
my_instance = my_class()

print(my_instance.bool_)

您应该找到此代码打印1。之所以可能这样做,是因为命名参数使用标准(词汇)作用域规则来解析值表达式,而参数名称本身在每次调用开始时便被注入名称空间。

如果您改为使用形式赋值将值绑定到类变量,则代码也将起作用

S.bool_ = bool_

因为在运行__init__方法时,类名S已经绑定在词法包围的名称空间中。即使这些值已绑定到该类,也可以相对于实例进行引用-由于它们不存在,因此解释程序会继续在实例的类中进行查找。

但是,无论哪种情况,都需要调用该类才能在__init__内设置属性值。

答案 2 :(得分:1)

让我们用另一个问题回答您的问题-您如何假设Python知道bool_行中的哪个bool_ = bool_

def gee(bool_, int32, int64, str_):

    class S:
        bool_ = bool_

第一个bool_是类属性吗?还是函数中的nonlocal bool_?还是“喘气”一个global对象?那么,分配的对象呢?= bool_应该指向哪个范围?

这就是为什么Python的口头禅是“显式优于隐式”。应该为Python很好地定义范围,以便理解您所指的范围,否则解释器会假定最直接的范围。

但是您已经知道解决该问题的方法-只需将其重命名为其他名称即可解决该问题,因为它明确地告诉Python解释器对象所指向的范围。

另一种方法是,您可以将类属性定义移出类范围:

def gee(bool_, int32, int64, str_):

    class S:
        pass

    S.bool_ = bool_
    S.int32 = int32
    ...
    return S

这样,您可以清楚地定义哪个bool_属于哪个范围。

答案 3 :(得分:0)

变量命名和范围是您的问题。如果将代码稍作更改,它应该可以工作。

def gee(some_bool, some_int32, some_int64, some_str):

    class S:
        bool_ = some_bool
        int32 = some_int32
        int64 = some_int64
        str_ = some_str

    return S

print(gee(1, 2, 3, 4))
# 1, 2, 3, 4

答案 4 :(得分:0)

您可以使用此代码。无需更改变量名称。您可以使用属性和类构造函数来解决此问题。

def gee(bool_, int32, int64, str_):

    class S:
        def __init__(self, bool_, int32, int64, str_):
            self.bool_ = bool_
            self.int32 = int32
            self.int64 = int64
            self.str_ = str_

        def get_bool_(self):
            return self.bool_

        def set_bool_(self, bool_):
            self.bool_ = bool_

        def get_int32(self):
            return self.int32

        def set_int32(self, int32):
            self.int32 = int32

        def get_int64(self):
            return self.int64

        def set_int64(self, int64):
            self.int64 = int64

        def get_str_(self):
            return self.str_

        def set_str_(self, str_):
            self.str_ = str_

    print(bool_, int32, int64, str_ )

    return S
g = gee(1, 2, 3, 4)

答案 5 :(得分:0)

一种可能性是使它们成为在 init 方法中分配的实例变量,从而将参数默认为调用中提供的值。