class_docstr: A data structure that contains DHT peers bucketed according to their distance to node_id.
  Follows Kademlia routing table as described in https://pdos.csail.mit.edu/~petar/papers/maymounkov-kademlia-lncs.pdf
  
  :param node_id: node id used to measure distance
  :param bucket_size: parameter $k$ from Kademlia paper Section 2.2
  :param depth_modulo: parameter $b$ from Kademlia paper Section 2.2.
  :note: you can find a more detailed description of parameters in DHTNode, see node.py
class_docstr_tok: ["A","data","structure","that","contains","DHT","peers","bucketed","according","to","their","distance","to","node_id",".","Follows","Kademlia","routing","table","as","described","in","https",":","\/\/pdos.csail.mit.edu\/~petar\/papers\/maymounkov-kademlia-lncs.pdf",":","param","node_id",":","node","id","used","to","measure","distance",":","param","bucket_size",":","parameter","$","k","$","from","Kademlia","paper","Section","2.2",":","param","depth_modulo",":","parameter","$","b","$","from","Kademlia","paper","Section","2.2.",":","note",":","you","can","find","a","more","detailed","description","of","parameters","in","DHTNode",",","see","node.py"]
docstr: Find k nearest neighbors from routing table according to XOR distance, does NOT include self.node_id
  
  :param query_id: find neighbors of this node
  :param k: find this many neighbors. If there aren't enough nodes in the table, returns all nodes
  :param exclude: if True, results will not contain query_node_id even if it is in table
  :return: a list of tuples (node_id, peer_id) for up to k neighbors sorted from nearest to farthest
docstr_tok: ["Find","k","nearest","neighbors","from","routing","table","according","to","XOR","distance",",","does","NOT","include","self.node_id",":","param","query_id",":","find","neighbors","of","this","node",":","param","k",":","find","this","many","neighbors",".","If","there","are","n't","enough","nodes","in","the","table",",","returns","all","nodes",":","param","exclude",":","if","True",",","results","will","not","contain","query_node_id","even","if","it","is","in","table",":","return",":","a","list","of","tuples","(","node_id",",","peer_id",")","for","up","to","k","neighbors","sorted","from","nearest","to","farthest"]
code: def get_nearest_neighbors(
      self, query_id: DHTID, k: int, exclude: Optional[DHTID] = None
  ) -> List[Tuple[DHTID, PeerID]]:
      """
      Find k nearest neighbors from routing table according to XOR distance, does NOT include self.node_id
  
      :param query_id: find neighbors of this node
      :param k: find this many neighbors. If there aren't enough nodes in the table, returns all nodes
      :param exclude: if True, results will not contain query_node_id even if it is in table
      :return: a list of tuples (node_id, peer_id) for up to k neighbors sorted from nearest to farthest
      """
      # algorithm: first add up all buckets that can contain one of k nearest nodes, then heap-sort to find best
      candidates: List[
          Tuple[int, DHTID, PeerID]
      ] = []  # min-heap based on xor distance to query_id
  
      # step 1: add current bucket to the candidates heap
      nearest_index = self.get_bucket_index(query_id)
      nearest_bucket = self.buckets[nearest_index]
      for node_id, peer_id in nearest_bucket.nodes_to_peer_id.items():
          heapq.heappush(
              candidates,
              (query_id.xor_distance(node_id), node_id, peer_id),
          )
  
      # step 2: add adjacent buckets by ascending code tree one level at a time until you have enough nodes
      left_index, right_index = (
          nearest_index,
          nearest_index + 1,
      )  # bucket indices considered, [left, right)
      current_lower, current_upper, current_depth = (
          nearest_bucket.lower,
          nearest_bucket.upper,
          nearest_bucket.depth,
      )
  
      while current_depth > 0 and len(candidates) < k + int(
          exclude is not None
      ):
          split_direction = (
              current_lower
              // 2 ** (DHTID.HASH_NBYTES * 8 - current_depth)
              % 2
          )
          # ^-- current leaf direction from pre-leaf node, 0 = left, 1 = right
          current_depth -= 1  # traverse one level closer to the root and add all child nodes to the candidates heap
  
          if (
              split_direction == 0
          ):  # leaf was split on the left, merge its right peer(s)
              current_upper += current_upper - current_lower
              while (
                  right_index < len(self.buckets)
                  and self.buckets[right_index].upper <= current_upper
              ):
                  for node_id, peer_id in self.buckets[
                      right_index
                  ].nodes_to_peer_id.items():
                      heapq.heappush(
                          candidates,
                          (
                              query_id.xor_distance(node_id),
                              node_id,
                              peer_id,
                          ),
                      )
                  right_index += 1
                  # note: we may need to add more than one bucket if they are on a lower depth level
              assert (
                  self.buckets[right_index - 1].upper == current_upper
              )
  
          else:  # split_direction == 1, leaf was split on the right, merge its left peer(s)
              current_lower -= current_upper - current_lower
              while (
                  left_index > 0
                  and self.buckets[left_index - 1].lower
                  >= current_lower
              ):
                  left_index -= 1  # note: we may need to add more than one bucket if they are on a lower depth level
                  for node_id, peer_id in self.buckets[
                      left_index
                  ].nodes_to_peer_id.items():
                      heapq.heappush(
                          candidates,
                          (
                              query_id.xor_distance(node_id),
                              node_id,
                              peer_id,
                          ),
                      )
              assert self.buckets[left_index].lower == current_lower
  
      # step 3: select k nearest vertices from candidates heap
      heap_top: List[Tuple[int, DHTID, PeerID]] = heapq.nsmallest(
          k + int(exclude is not None), candidates
      )
      return [
          (node, peer_id)
          for _, node, peer_id in heap_top
          if node != exclude
      ][:k]
  
imports: from __future__ import annotations
  import hashlib
  import heapq
  import os
  import random
  from collections.abc import Iterable
  from itertools import chain
  from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union
  from hivemind.p2p import PeerID
  from hivemind.utils import MSGPackSerializer, get_dht_time
label_desc: Use image node_id 10 for calling the RoutingTable obj's underlying member method code with example usage: obj.get_nearest_neighbors(query_id, k, exclude) and returns: unknown