【问题标题】:Function will not return a value after it calls itself函数调用自身后不会返回值
【发布时间】:2020-11-07 12:35:21
【问题描述】:

我几天前刚开始学习 Python。我正在开发一个国际象棋游戏,而我遇到的问题是确定玩家想要下的棋子位置的代码。如果我输入一个包含两个数字的字符串,该函数会将它们解析出来并分配它们以及 y 和 x 值。

但是,如果在字符串中只找到一个数字或没有找到数字,它会打印“No digits or not enough numbers found”,让您重新输入一个字符串,然后再次调用该函数。我的问题是,在它调用自己之后,如果你输入一个有效的字符串,它会识别数字,将它打包到一个列表中,就像它应该做的那样,但不会返回任何东西。我检查以确保列表已正确填写,它只是返回无。

这是错误:

Traceback (most recent call last):
  File "C:\Users\Jack\PycharmProjects\Messing With 2D Lists\Messing With 2D Lists.py", line 91, in <module>
    piece_num = position[0]
TypeError: 'NoneType' object is not subscriptable

这是调用函数的代码,最后一行是触发错误的代码,因为它接收的不是一个列表,而是一个无类型:

posit = input("Choose your piece\nEnter coordinates: ")

position = piece_position(posit)

print(position)

piece_num = position[0]

这里是“棋盘”,如果你想重现我的错误,因为它在代码中被引用。

chess = [
    [1, 2, 1, 2, 1, 2, 1, 2],
    [2, 1, 2, 1, 2, 1, 2, 1],
    [1, 2, 1, 2, 1, 2, 1, 2],
    [2, 1, 2, 1, 2, 1, 2, 1],
    [1, 2, 1, 2, 1, 2, 1, 2],
    [2, 1, 2, 1, 2, 1, 2, 1],
    [1, 2, 1, 2, 1, 2, 1, 2],
    [2, 1, 2, 1, 6, 1, 2, 1],

]

这里是确定棋子位置的函数:

def piece_position(pos):
    
    num1d = False
    num2d = False

    for char in pos:
        if not num1d:
            if char.isdigit():
                num1 = int(char) - 1
                num1d = True
            else:
                pass
        elif not num2d:
            if char.isdigit():
                num2 = int(char) - 1
                num2d = True
            else:
                pass

    print(num1d)
    print(num2d)

    if num1d and num2d:
        print(num1, num2)
        result = [chess[num1][num2]]
        result.append(num1)
        result.append(num2)
        print(result)
        return result
    else:
        print("No digits or not enough digits found")
        posit2 = input("Choose your piece\nEnter coordinates: ")
        piece_position(posit2)

我还知道当它翻转时我将其称为 posit2 而不是仅是 posit,并且该变量正在询问 posit 的结果,但是将其更改为 posit 或 posit2 似乎没有任何区别。

请随意提出建设性的组织批评,我还是 Python 的新手,并且还在摸索中,我知道我还有很多需要改进的地方,还有很多我不明白的地方。

【问题讨论】:

  • 你只需要调用带有return语句的递归函数:return piece_position(posit2)
  • @SerialLazer 感谢您的快速回复!我究竟会把它放在哪里?我是否需要一些索引来标记它是否是第二次或更长时间运行,如果是则返回你说的递归语句?
  • 只需将 return 添加到调用递归函数的代码的最后一行。
  • @SerialLazer 谢谢,在你回复之前我意识到,我很感激!
  • 另外,最好使用while循环而不是在其内部调用函数

标签: python python-3.x return-type


【解决方案1】:

你会在重复函数的 else 语句中得到 None ,你可以返回函数调用结果,或者你可以只使用一个 while 循环

def piece_position(pos):
    
    num1d = False 
    num2d = False

    while not num1d and not num2d:
        for char in pos:
            if not num1d:
                if char.isdigit():
                    num1 = int(char) - 1
                    num1d = True
            elif not num2d:
                if char.isdigit():
                    num2 = int(char) - 1
                    num2d = True
        if not num1d and not num2d:
            pos = input("Try again:") 
            num1d = False 
            num2d = False

    result = []
    #... 
    return result

或者,可能更简单的是在函数体外部而不是内部做同样的事情(这种方式肯定更容易测试)

pos = input("Enter coordinates") 
result = piece_position(pos)
while not result:  # assuming this returned None, or an empty list, for example 
    pos = input("Enter coordinates") 
    result = piece_position(pos)

【讨论】:

  • 非常感谢!这一切都为我解决了,我必须对您所做的调整进行的唯一更改是在您的第一个解决方案中的每个循环之后将 num1d 和 num2d 都重置为 False ,否则可能仍然会卡在 True 并最终重新分配新的第一位数字作为第二位数字。非常感谢您对此提供的帮助以及您的建议,以使其更整洁、更简单。
【解决方案2】:

虽然你已经解决了,但再次:你在递归调用中忘记了return

def func(n):
    ...

    return func(n')

另外,为什么要检查输入是否为数字?这几乎和通过isinstance 进行检查一样糟糕。这不是C,我们宁愿根本不检查它

如果您无论如何要将值转换为int,请先尝试,然后捕获错误。这就是EAFP 风格。

try:
    a = int('Nope.')  # try first
except ValueError:
    pass  # Fail, then that's a sad thing. Do some actions for this rouge cases.
else:
    pass  # We're good, keep going and do something with a. 

更简单的实现方式如下:

chess = [
    [1, 2, 1, 2, 1, 2, 1, 2],
    [2, 1, 2, 1, 2, 1, 2, 1],
    [1, 2, 1, 2, 1, 2, 1, 2],
    [2, 1, 2, 1, 2, 1, 2, 1],
    [1, 2, 1, 2, 1, 2, 1, 2],
    [2, 1, 2, 1, 2, 1, 2, 1],
    [1, 2, 1, 2, 1, 2, 1, 2],
    [2, 1, 2, 1, 6, 1, 2, 1],
]

def validate_position(board, pos: str) -> (int, int):
    try:
        x, y = map(int, pos.split())  # Fail-fast
    except ValueError as err:
        raise ValueError(f"Not enough / Too many values, expected 2!") from err

    # put some rule check here.
    if (0 <= x < len(board)) and (0 <= y < len(board[0])):
        return board[x][y], x, y

    raise ValueError(f"Position {(x, y)} is outside of board!")


def piece_position(board):
    inp = input("Enter coordinates (x y): ")  # Expecting 1 10 format
    try:
        return validate_position(board, inp)
    except ValueError as err:
        print(err)
        return piece_position(board)


print(f"Selected: {piece_position(chess)}")

Enter coordinates (x y): 1 2 3
Not enough / Too many values, expected 2!
Enter coordinates (x y): 1
Not enough / Too many values, expected 2!
Enter coordinates (x y): 10 3
Position (10, 3) is outside of board!
Enter coordinates (x y): 2 3
Selected: (2, 2, 3)

如果将 x,y 坐标放在不同的input() 调用是绝对强制的,更改如下:

inp = input("Enter coordinates (x y): ")  # Expecting 1 10 format

# to 

inp = " ".join(input("Enter X: "), input("Enter Y: "))

正如您所说,您是python 新手,您最好了解python 中递归的缺点。两者都不适用于您的用例,但更好地了解未来的情况。

>>> def recurse(called=1):
...     print(called)
...     return recurse(called + 1)
... 
>>> print(recurse())
.
.
.
985
986
987
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 3, in recurse
  File "<input>", line 3, in recurse
  File "<input>", line 3, in recurse
  [Previous line repeated 984 more times]
  File "<input>", line 2, in recurse
RecursionError: maximum recursion depth exceeded while calling a Python object

Python不进行尾递归优化,由于CPython栈限制,很容易造成栈溢出,引发RecursionError。当然,我们永远不会在大约 1000 次迭代中输入错误。

另一个缺点是性能。

from functools import lru_cache
import timeit


@lru_cache(512)
def fibo_recurse_cached(n):
    if n == 0:
        return 0
    if n == 1:
        return 1

    return fibo_recurse_cached(n - 2) + fibo_recurse_cached(n - 1)


def fibo_recurs(n):
    if n == 0:
        return 0
    if n == 1:
        return 1

    return fibo_recurs(n - 2) + fibo_recurs(n - 1)


def fibo_gen(n):
    a, b = 1, 1
    for _ in range(n):
        yield a
        a, b = b, a + b


print(f"Naive recursive : {timeit.timeit(lambda: fibo_recurs(38), number=10)}")
print(f"Cached recursive: {timeit.timeit(lambda: fibo_recurse_cached(38), number=10)}")
print(f"Generator ver.  : {timeit.timeit(lambda: list(fibo_gen(38)), number=10)}")
Naive recursive : 136.2449525
Cached recursive: 1.939999998512576e-05
Generator ver.  : 4.369999999198626e-05

如果您在递归内部执行的操作很简单并且与外部范围没有任何交互,那么functools.lru_cache 将大大提高性能。


【讨论】:

  • 嘿,这太棒了!感谢您抽出宝贵的时间来写这篇文章,我仍在阅读它,但我非常感谢您为此付出的努力。我一定会考虑到这一点。
  • 一个人不必陷入其他人已经做过的陷阱,至少在我看来:这是我的信念。看看python glossary 会让你对 Python 的哲学有一个很好的了解。
  • 我会检查一下谢谢。我刚刚读完所有这些,这些绝对是关于递归函数的重要点。我将不得不重新阅读最后一个几次以确保我理解它。同样正如 OneCricketeer 建议的那样,我继续重写它以适应 while 循环而不是使用递归。
猜你喜欢
  • 2022-01-01
  • 2013-07-06
  • 2013-03-25
  • 1970-01-01
  • 2014-07-14
  • 2022-06-11
  • 2016-05-31
  • 2021-11-17
相关资源
最近更新 更多