【问题标题】:Returning a counter outside of a Python Function在 Python 函数之外返回一个计数器
【发布时间】:2013-08-29 21:48:58
【问题描述】:

我正在尝试构建一些代码,并且我已经定义了一个函数来测试计数器在函数内部的工作方式:

def errorPrinting(x):
    x += 1
    return x

然后我在一些条件逻辑中使用该函数,如果满足条件,我希望计数器增加。

x = 1

for row in arcpy.SearchCursor(fc):              

    if not row.INCLUSION_TYPE or len(row.TYPE.strip()) == 0:
        errorPrinting(x)
        print x

    elif len(row.TYPE) not in range(2,5):
        errorPrinting(x)
        print x

    elif row.INCLUSION_TYPE.upper() not in [y.upper() for y in TableList]:
        errorPrinting(x)
        print x

我对使用函数还是很陌生,所以也许我不明白如何将值返回到函数之外,以便在 for 循环的下一次迭代中使用。它一直在我身上返回 1。任何人都可以告诉我如何在函数外部返回 x 增加一个 x+= 1 后的 x 吗?

谢谢, 迈克

【问题讨论】:

  • arcpy... 对不起,先生。除此之外,您到底想在 errorPrinting 函数中做什么?我知道您想要一个计数器,但是除了增加 x 之外,您还想做什么?在这段代码中,errorPrinting 可以简单地替换为x += 1,这样就可以了。那么您希望通过使用该功能获得什么?我认为,这个问题的答案可能会严重影响答案中的一些结构性建议。

标签: python counter


【解决方案1】:

您不是在增加全局 x,而是在增加也恰好名为 x 的本地参数! (您的 errorPrinting 参数可以命名为任何名称。我称之为 xLocal。)

正如您在此处看到的,x 不会被函数递增。

>>> def inc(xLocal):
...     xLocal += 1
...     return xLocal
... 
>>> x = 4
>>> inc(x)
5
>>> x
4

每次都需要将x的值重新赋值给函数的返回值。像这样

x = 1
for row in arcpy.SearchCursor(fc):              

    if not row.INCLUSION_TYPE or len(row.TYPE.strip()) == 0:
        x = errorPrinting(x) # <=== here
        print x

    elif len(row.TYPE) not in range(2,5):
        x = errorPrinting(x) # <=== here
        print x

    elif row.INCLUSION_TYPE.upper() not in [y.upper() for y in TableList]:
        x = errorPrinting(x) # <=== here
        print x

整体参数和其他原语通常不会在 Python 中通过引用传递。 (列表、dict等都是。无意中修改列表实际上是Python中很常见的错误。)

编辑:通过“引用”和“值”传递在 Python 中并不是很正确。有关详细信息,请参阅 this nice question。

所以,用我之前的例子:

>>> x = 4
>>> x = inc(x)
>>> x
5

请注意,如果这是通过引用传递的参数,例如列表,则此策略会起作用。

>>> def incList(xList):
...     for i in range(len(xList)):
...         xList[i] += 1
... 
>>> xList
[1]
>>> incList(xList)
>>> xList
[2]

注意正常的 Pythonic 语法:

for i in xList:
    i += 1

不会增加全局值。

注意:如果您希望密切关注很多事情,我还推荐@SB 的logging 模块。提及。它非常有用,使调试大型程序变得容易得多。您可以获取时间、消息类型等。

【讨论】:

  • 小心地说“参数通常不会在 python 中通过引用传递。” - 原语通常按值/复制,但像 list/dict/obj 这样的东西是参考。
  • 对于 Python 如何处理传递参数,我并不特别喜欢“按引用传递/按值传递”语言。问题在于,在底层实现中,所有内容都以相同的方式传递,但是赋值区分“可变”和“不可变”对象,并且在赋值时不可变对象的引用被替换为不同的对象。
  • @MarkR.Wilkins 非常好。我发现自己陷入了不正确的术语。我将编辑此答案以指向包含更多信息的链接。
  • @MarkR.Wilkins 分配真的能区分任何东西吗?我认为x = 1x = {} 的工作方式相同。碰巧一个分配一个不可变对象(在这种情况下由文字表示),另一个是可变对象。一个函数可以修改 dict 参数,因为它是可变的,但是如果它为变量分配一个新的 dict 而不是修改变量指向的 dict,那么这并没有什么不同。还是我错过了什么?
  • 好吧,至少索引分配对于不同类型的实现方式不同。 mytuple[0] = 3 会抛出异常,而 mylist[0] = 3 不会。区别发生在尝试分配时。
【解决方案2】:

你被范围所困扰。您可能想查看此link 以获得快速入门。

您可以做一些简单的事情并说x = errorPrinting(x) 在所有情况下您调用errorPrinting 并得到您想要的。但我认为有更好的解决方案可以让您了解更多信息。

考虑实现一个为您维护计数的错误记录器对象。然后您可以执行 logger.errorPrinting() 并且您的 logger 实例将管理计数器。您可能还想查看 python 的built in logging facilities

【讨论】:

    【解决方案3】:

    为 OP 的利益而编辑,因为如果函数是一个新概念,我早期的 cmets 可能有点难以理解。

    我个人认为解决此问题的最佳方法是将相关代码包装在一个对象中。

    Python 很大程度上基于对象的概念,您可以将其视为将数据与对数据进行操作的函数进行分组。一个对象可能代表一个事物,或者在某些情况下可能只是让一些相关函数共享一些数据的一种方便方式。

    对象被定义为“类”,它定义了对象的类型,然后您创建“实例”,每个实例都是类中定义的数据分组的单独副本。

    class MyPrint(object):
    
        def __init__(self):
            self.x = 1
    
        def errorPrinting(self):
            self.x += 1
            return self.x
    
        def myPrint(self):
            for row in arcpy.SearchCursor(fc):              
    
                if not row.INCLUSION_TYPE or len(row.TYPE.strip()) == 0:
                    self.errorPrinting()
                    print self.x
    
                elif len(row.TYPE) not in range(2,5):
                    self.errorPrinting()
                    print self.x
    
                elif row.INCLUSION_TYPE.upper() not in [y.upper() for y in TableList]:
                    self.errorPrinting()
                    print self.x
    
    p = MyPrint()
    p.myPrint()
    

    函数__init__(self)errorPrinting(self)myPrint(self) 都称为“方法”,它们是为类中的任何对象定义的操作。为类的实例对象之一调用这些函数会自动将self 参数放在任何包含对调用该函数的特定实例的引用的参数前面。 self.x 指的是由该实例对象存储的变量,因此函数可以共享该变量。

    看起来像是对类名的函数调用:

    p = MyPrint()
    

    实际上是新建一个MyPrint类的实例对象,调用MyPrint.__init__(&lt;instance&gt;),其中&lt;instance&gt;是新对象,然后将实例赋值给p。然后,调用

    p.myprint()
    

    致电MyPrint.myprint(p)

    这有一些好处,因为您以这种方式使用的变量只会在需要对象时持续存在,您可以为执行相同操作的不同任务设置多个计数器,并且范围都得到了处理,另外您不会弄乱全局命名空间,也不必在函数之间传递值。

    【讨论】:

      【解决方案4】:

      最简单的修复方法,虽然可能不是最好的样式:

      def errorPrinting():
          global x
          x += 1
      

      然后将x=errorPrinting(x)转换为errorPrinting()

      “global x”使函数使用全局定义的x,而不是在函数范围内创建一个。

      其他的例子都很好。研究所有这些。

      【讨论】:

      • 将名称空间与全局状态混为一谈对于像计数器这样简单的东西并不是一个好的选择。
      • Benjamin - 这取决于... 在一个独立的程序中,有一些用于分离关注点的函数和一些时间限制,在美国喜剧白话中使用 global may “git 'er done”。设计一个类或一个可重用的组件——太可怕了。变得虔诚...
      • 正确。无论如何,如果您编辑答案,我只能删除我的反对票(系统限制)。铜左右
      猜你喜欢
      • 2021-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-27
      • 1970-01-01
      • 2022-07-06
      • 2016-04-21
      • 2015-10-16
      相关资源
      最近更新 更多