不使用 "global" 关键字,在函数外部访问函数变量的方法

79

我正试图在Python函数外访问本地函数变量。

我可以通过全局变量使代码像这样工作:

我正在尝试在 Python 函数之外访问函数内的本地变量。

我可以通过使用全局变量来让以下代码运行:

bye = ''
def hi():
    global bye
    bye = 5
    sigh = 10

hi()
print(bye)

接下来,我尝试了这段代码,希望能够在不使用 global bye 的情况下访问 hi() 之外的 bye

```python def hi(): bye = "Goodbye" return bye greeting = hi() print(greeting) print(bye) # This line throws a NameError ```
def hi():
    bye = 5 
    sigh = 10
    return

hi()
x = hi()
print(x.bye)

这导致出现AttributeError: 'NoneType' object has no attribute 'bye'的错误。

接下来,我尝试了:

def hi():
    bye = 5
    sigh = 10
    return bye

hi()
x = hi()
print(x.bye)

这没有改善情况; 我收到了 AttributeError: 'int' object has no attribute 'bye' 错误信息。

有没有一种方法可以在不使用全局变量和不打印出 sigh 变量的情况下访问本地函数变量 (bye) 之外的函数 (hi())?我该如何做到这一点?


6
bye = hi() print bye翻译:将以上代码翻译成中文,输出变量 bye 通过调用函数 hi() 的返回值。 - Ashwini Chaudhary
2
你想要实现什么?使用场景是什么? - Rod
@Rod - 我的使用情况涉及在函数外部使用局部变量。同时,该函数有多个变量,因此我已经包含了sigh变量。 - askance
3
你真的只是对学习如何使用return感兴趣吗? - binki
2
如果你正在使用函数来计算一个值,然后在函数外部使用该值,那么你最好直接返回计算出的值。根据你展示的代码,你不需要静态变量,也不需要通过函数本身访问该值。你只是想弄清楚如何将计算出的值传递给调用者。返回值就是return所做的事情。请参考这个代码片段,并让我知道。 - binki
显示剩余2条评论
6个回答

142
您可以按照以下方式进行操作(在我测试时,这适用于Python v2.7.17和v3.8.1):
def hi():
    # other code...
    hi.bye = 42  # Create function attribute.
    sigh = 10

hi()
print(hi.bye)  # -> 42

在Python中,函数是对象,可以将任意属性分配给它们。

如果您经常这样做,您可以通过创建一个函数装饰器来实现更通用的东西,该装饰器会将一个this参数添加到每个调用装饰的函数中。

这个额外的参数将使函数能够引用自身,而不需要显式地嵌入(硬编码)其名称到其余的定义中。类方法自动接收第一个参数作为实例参数,通常命名为self,这与实例参数类似——我选择了一些不同的名称以避免混淆,但像self参数一样,它可以被命名为您希望的任何名称。

下面是一个示例:

def add_this_arg(func):
    def wrapped(*args, **kwargs):
        return func(wrapped, *args, **kwargs)
    return wrapped

@add_this_arg
def hi(this, that):
    # other code...
    this.bye = 2 * that  # Create function attribute.
    sigh = 10

hi(21)
print(hi.bye)  # -> 42

注意

这不适用于类方法。只需使用传递给方法的实例参数,按照惯例命名为self,而不是方法名称。您可以通过 type(self) 引用类级别属性。请参见在类中时函数的属性


16
欢迎,但我必须补充一点,这种做法相当不寻常。它可能很难调试,并且会使代码难以维护--因为本质上它与使用全局变量是一样的,而全局变量长期以来一直被认为是有害的。 - martineau
2
如定义所示,hi()函数没有返回值,因此它实际上返回None,被认为是一个False值,因此if不会执行条件print语句。 - martineau
1
正如我之前所说,它确实可以工作,至少对我来说是这样的,使用Python 3.6.3(真的没有理由为什么它不会)-我刚刚再次运行了它以确保。如果它对你不起作用,那么肯定有其他问题。请注意,在尝试访问属性之前,您必须至少调用该函数一次,以便创建它。 - martineau
1
这远远超出了OP实际寻找的范围。 - Karl Knechtel
1
@tripleee:这基本上就是我在第一条评论中所说的 - 也许你错过了。 - martineau
显示剩余7条评论

12
问题在于你在将x设置为字符串后调用了print(x.bye)。当你运行x = hi()时,它会运行hi()并将x的值设置为5(bye的值;它不会将x的值设置为bye变量本身的引用)。例如:bye = 5; x = bye; bye = 4; print(x)打印的是5而不是4。
另外,你不必两次运行hi(),只需运行x = hi(),而不是hi(); x=hi()(按照你的方式,它运行了hi(),没有对结果值5进行任何处理,然后重新运行了相同的hi()并将值5保存到x变量中)。
因此,完整的代码应该是:
def hi():
    bye = 5
    sigh = 10
    return bye 
x = hi()
print(x)

如果您想要返回多个变量,一种选项是使用列表或字典,具体取决于您需要的内容。例如:

def hi():
    return { 'bye': 5, 'sigh': 10 }
x = hi()
print x['bye']

1
谢谢。这很有帮助。虽然我无法摆脱这样的感觉,即有一种干净/文档化的技巧可以实现它 - 定义多个本地变量,然后在函数外部调用其中一个。 - askance

3
为了能够访问本地函数的变量,可以在变量名之前添加函数名称和一个点(然后,在函数体内和外部调用变量时都使用此构造方式)。这个解决方案适用于Python 3.7.4。
例如:
def func(): # define a function
    # here y is a local variable, which I want to access; func.y 
    # defines a method for my example function which will allow me to  
    # access function's local variable y
    func.y = 4 
    x = func.y + 8
    return x

func() # now I'm calling the function
a = func.y # I put its local variable into my new variable
print(a) # and print my new variable

1
这个重复了2013年的被接受的答案。 - tripleee
1
@tripleee,如果这个回答与被接受的答案重复了,我就不会发表它。你为什么认为我发表它,是因为我喜欢打字吗?我尝试了被接受的答案,但它没有起作用,所以我自己想出了解决方案。而且,正如你可能注意到的那样,我的答案有两个赞。显然,我不是唯一一个无法利用被接受的答案的人。此外,在Karl Knechtel编辑我的答案之前,它包括了一些评论,在那里 - 如果我记得正确的话 - 我指出被接受的答案对我没有用,并且我的解决方案基于其他答案(它们本身对我也没有用)。 - Ivan Nepomnyashchikh
1
这很容易从修订历史记录中检查(该历史记录链接在通知中,告诉您谁进行了最新的编辑)。 - tripleee
1
你的答案中的代码与被接受的答案的前半部分基本相同,而且仍然可以正常工作。 - tripleee

3
 def hi():
     bye = 5
     return bye  

print hi()

def hi(): bye = 5 return bye bye = 5; print("bye")(注:此处是代码,非自然语言) - askance

2
您可以沿着以下思路进行操作:
def static_example():
   if not hasattr(static_example, "static_var"):
       static_example.static_var = 0
   static_example.static_var += 1
   return static_example.static_var

print static_example()
print static_example()
print static_example()

1

如果您想避免使用 global,一种可能的方法是定义一个类。每个类实例都有自己的属性;还有一个类属性空间,实例之间可以共享一个属性。

如果您是 Python 新手,那么面向对象编程可能会让您感到挑战,但这也许是开始尝试它的好时机。

class Thing:
    shared = "foo"

    def __init__(self):
        """
        This gets called when you create a new Thing()
        """
        self.bar = "baz"  # default value for new instances

    def get_bar(self):
        return self.bar

    def set_bar(self, value):
        self.bar = value

现在,让我们创建两个实例。
first = Thing()
second = Thing()

get_barset_bar方法在这个简单的例子中并不是必需的。你也可以这样做:

second.bar = "ick"
print(second.bar)
# "ick"
print(first.bar)
# "baz"

尽管对于更复杂的场景,您可能希望要求用户调用setter和getter方法;有一些强制执行的方法-例如参见What's the pythonic way to use getters and setters?

如果通过一个实例更改类属性,则其他实例中将不会更改该属性。

second.shared = "poo"
print(first.shared)
# "foo"

但是,如果您在类本身中进行更改,则会在所有未单独覆盖共享值的实例中进行更改。

Thing.shared = "zoom"
print(first.shared)
# "zoom"
print(second.shared)
# "poo", still

简而言之,要创建一个新的Thing实例,您需要调用Thing()方法;这会在返回新实例之前运行__init__方法。在类内部,实例是每个(非静态,非类)方法的第一个参数,并且通常称为self(尽管如果您愿意,可以将其称为shirley,因为对于Python解释器来说,这是可以接受的)。类有很多其他功能;主要卖点可能是您可以创建从父类继承但可以覆盖某些行为的子类(常见示例通常涉及现实世界的概念,如动物或车辆,但类可以是任何需要创建类型并封装其行为以及在派生类型中重写一些方法的东西)。

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