docs: def assemble(ops, target=None):
      """
      Assemble a set of :class:`Op` and :class:`Label` instance back into
      bytecode.
  
      Arguments:
          ops(list): A list of opcodes and labels (as returned by
              :func:`disassemble`).
          target: The opcode specification of the targeted python
              version. If this is ``None`` the specification of the currently
              running python version will be used.
  
      Returns:
          bytes: The assembled bytecode.
      """
  
  
      target = get_py_internals(target)
  
      opmap = target['opmap']
      hasjrel = target['hasjrel']
      hasjabs = target['hasjabs']
      hasjump = set(hasjrel) | set(hasjabs)
      have_argument = target['have_argument']
      extended_arg = target['extended_arg']
      wordcode = target['wordcode']
  
      if not wordcode:
          def encode_op(output, op_code, op_arg=None):
              n = 1
              if op_arg is None:
                  output.append(op_code)
              else:
                  n += 2
                  ext_arg = op_arg >> 16
                  if ext_arg:
                      n += 3
                      output.extend([extended_arg, ext_arg & 255, ext_arg >> 8])
                      op_arg &= 65535
                  output.extend([op_code, op_arg & 255, op_arg >> 8])
              return n
      else:
          def encode_op(output, op_code, op_arg=None):
              n = 2
              if op_arg is None:
                  output.extend([op_code, 0])
              else:
                  ext_arg = op_arg >> 8
                  if ext_arg:
                      n += encode_op(extended_arg, ext_arg)
                  output.extend([op_code, op_arg & 255])
              return n
  
      # A bit of a chicken and egg problem: The address of a label depends on the instructions before it. However,
      # the instructions before a label might depend on the label itself: For very large functions, jumps may
      # require an EXTENDED_ARG opcode if the jump destination is far away. Which we only know when the label
      # has materialized, which means the address of the label will change on the next pass, which might mean
      # a different jump offset might become larger, etc... We run passes until no label changes address.
  
      label_address = {}
      while True:
          retry = False
          output = bytearray()
          address = 0
  
          for op in ops:
              if isinstance(op, Label):
                  if label_address.get(op) != address:
                      retry = True
                      label_address[op] = address
                  continue
  
              op_code = opmap[op.name]
              op_arg = op.arg
  
              if op_code >= have_argument and op_arg is None:
                  # Sanity check.
                  raise ValueError('Opcode %s requires argument.' % op)
              elif op_code < have_argument and op_arg is not None:
                  # Sanity check.
                  raise ValueError('Opcode %s should not have an argument.' % op)
              elif isinstance(op_arg, Label):
                  if op_code not in hasjump:
                      # Sanity check.
                      raise ValueError('Did not expect label as argument for opcode %s.' % op)
  
                  if op_arg not in ops:
                      # Sanity check.
                      raise ValueError('Label is not part of this op list.')
  
                  # Try to turn the label argument into an address.
                  op_arg = label_address.get(op_arg)
                  if op_arg is None:
                      # Label hasn't materialized yet, we'll catch it on the next pass.
                      address += encode_op(output, op_code, 0)
                      continue
  
                  if op_code in hasjrel:
                      op_arg -= address
              elif op_code in hasjump:
                  # Sanity check.
                  raise ValueError('Expected label as argument for opcode %s.' % op)
  
              # Encode the opcode and the argument.
              n = encode_op(output, op_code, op_arg)
              address += n
  
              if op_code in hasjrel:
                  if not wordcode:
                      op_arg = output[-2] + (output[-1] << 8)
                      if op_arg < n:
                          ext_arg = output[-5] + (output[-4] << 8) - 1
                          output[-5], output[-4] = ext_arg & 255, ext_arg >> 8
                          op_arg += 65536
                      op_arg -= n
                      output[-2], output[-1] = op_arg & 255, op_arg >> 8
                  else:
                      for i in itertools.count(1, 2):
                          if n <= output[-i]:
                              output[-i] -= n
                              break
                          output[-i] += 256 - n
                          n = 1
  
          if not retry:
              return bytes(output)