简体   繁体   中英

Python's nonlocal keyword - is this good practice?

Consider a simple situation such as finding the k'th smallest element in a BST.

In my solution below:

class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        i = 0
        ans = -1
        def traverse_inorder(root):
            nonlocal i
            nonlocal ans
            if not root:
                return
            traverse_inorder(root.left)
            i += 1
            if i == k:
                ans = root.val
                return
            traverse_inorder(root.right)
        traverse_inorder(root)
        return ans

Is my use of nonlocal for i and ans good practice? I do this to keep track of how many elements I've traversed after reaching the left most node (smallest value).

Another solution would be to have i and ans as member variables of the class:

class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        self.i = 0
        self.ans = -1
        def traverse_inorder(root):
            # etc etc

Are both methods equivalent? Is one practice better than the other, and why?

I think that under the circumstances you describe, the nonlocal approach objectively makes more sene than the other one you show. Your goal is to have a counter outside your function and a way to keep track of the result when it is found regardless of where you are in the recursion.

nonlocal completely encapsulates that information in a namespace dedicated to the particular run of your outer function. There is really no downside here.

Using instance attributes makes that information available to anyone using the instance. This is unnecessary conceptually, marginally slower, and not thread-safe. While thread safety is often not a concern in Python, it does make your code much less robust. Between that and encapsulation, I would definitely stay away from this approach.

I would suggest a third possibility here: using return values. In that case, your really don't need any external namespace. I don't necessarily recommend this over using nonlocal , but it's worth mentioning. Here is a sample implementation, which as you can see is much more verbose than your solution:

class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        def traverse_inorder(root, target):
            if not root:
                return 0, None
            left_count, item = traverse_inorder(root.left, target)
            if left_count == target - 1:
                 return left_count + 1, root.val
            elif left_count < target:
                 right_count, item = traverse_inorder(root.right, target - left_count - 1)
                 return left_count + right_count + 1, item
            else:  # left_count == target
                 return left_count, item
        count, ans = traverse_inorder(root, k)
        if count < k:
            raise ValueError('Insufficient elements')
        return ans

There are many ways to do this with return values. Here, I compute the number of elements in each subtree, up to the target value. A non-none item is only returned if the exact number of elements is found.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM