def type_to_py_container()

in tensorflow_federated/python/core/impl/types/type_conversions.py [0:0]


def type_to_py_container(value, type_spec):
  """Recursively convert `structure.Struct`s to Python containers.

  This is in some sense the inverse operation to
  `structure.from_container`.

  Args:
    value: A structure of anonymous tuples of values corresponding to
      `type_spec`.
    type_spec: The `tff.Type` to which value should conform, possibly including
      `computation_types.StructWithPythonType`.

  Returns:
    The input value, with containers converted to appropriate Python
    containers as specified by the `type_spec`.

  Raises:
    ValueError: If the conversion is not possible due to a mix of named
      and unnamed values, or if `value` contains names that are mismatched or
      not present in the corresponding index of `type_spec`.
  """
  if type_spec.is_federated():
    if type_spec.all_equal:
      structure_type_spec = type_spec.member
    else:
      if not isinstance(value, list):
        raise TypeError('Unexpected Python type for non-all-equal TFF type '
                        f'{type_spec}: expected `list`, found `{type(value)}`.')
      return [
          type_to_py_container(element, type_spec.member) for element in value
      ]
  else:
    structure_type_spec = type_spec

  if structure_type_spec.is_sequence():
    element_type = structure_type_spec.element
    if isinstance(value, list):
      return [type_to_py_container(element, element_type) for element in value]
    if isinstance(value, tf.data.Dataset):
      # `tf.data.Dataset` does not understand `Struct`, so the dataset
      # in `value` must already be yielding Python containers. This is because
      # when TFF is constructing datasets it always uses the proper Python
      # container, so we simply return `value` here without modification.
      return value
    raise TypeError('Unexpected Python type for TFF type {}: {}'.format(
        structure_type_spec, type(value)))

  if not structure_type_spec.is_struct():
    return value

  if not isinstance(value, structure.Struct):
    # NOTE: When encountering non-`structure.Struct`s, we assume that
    # this means that we're attempting to re-convert a value that
    # already has the proper containers, and we short-circuit to
    # avoid re-converting. This is a possibly dangerous assumption.
    return value

  container_type = structure_type_spec.python_container

  # Ensure that names are only added, not mismatched or removed
  names_from_value = structure.name_list_with_nones(value)
  names_from_type_spec = structure.name_list_with_nones(structure_type_spec)
  for value_name, type_name in zip(names_from_value, names_from_type_spec):
    if value_name is not None:
      if value_name != type_name:
        raise ValueError(
            f'Cannot convert value with field name `{value_name}` into a '
            f'type with field name `{type_name}`.')

  num_named_elements = len(dir(structure_type_spec))
  num_unnamed_elements = len(structure_type_spec) - num_named_elements
  if num_named_elements > 0 and num_unnamed_elements > 0:
    raise ValueError(
        f'Cannot represent value {value} with a Python container because it '
        'contains a mix of named and unnamed elements.\n\nNote: this was '
        'previously allowed when using the `tff.structure.Struct` container. '
        'This support has been removed: please change to use structures with '
        'either all-named or all-unnamed fields.')
  if container_type is None:
    if num_named_elements:
      container_type = collections.OrderedDict
    else:
      container_type = tuple

  # Avoid projecting the `structure.StructType`d TFF value into a Python
  # container that is not supported.
  if (num_named_elements > 0 and
      is_container_type_without_names(container_type)):
    raise ValueError(
        'Cannot represent value {} with named elements '
        'using container type {} which does not support names. In TFF\'s '
        'typesystem, this corresponds to an implicit downcast'.format(
            value, container_type))
  if (is_container_type_with_names(container_type) and
      len(dir(structure_type_spec)) != len(value)):
    # If the type specifies the names, we have all the information we need.
    # Otherwise we must raise here.
    raise ValueError('When packaging as a Python value which requires names, '
                     'the TFF type spec must have all names specified. Found '
                     '{} names in type spec {} of length {}, with requested'
                     'python type {}.'.format(
                         len(dir(structure_type_spec)), structure_type_spec,
                         len(value), container_type))

  elements = []
  for index, (elem_name, elem_type) in enumerate(
      structure.iter_elements(structure_type_spec)):
    element = type_to_py_container(value[index], elem_type)

    if elem_name is None:
      elements.append(element)
    else:
      elements.append((elem_name, element))

  if (py_typecheck.is_named_tuple(container_type) or
      py_typecheck.is_attrs(container_type) or
      container_type is tf.SparseTensor):
    # The namedtuple and attr.s class constructors cannot interpret a list of
    # (name, value) tuples; instead call constructor using kwargs. Note that
    # these classes already define an order of names internally, so order does
    # not matter.
    return container_type(**dict(elements))
  elif container_type is tf.RaggedTensor:
    elements = dict(elements)
    return tf.RaggedTensor.from_nested_row_splits(elements['flat_values'],
                                                  elements['nested_row_splits'])
  else:
    # E.g., tuple and list when elements only has values, but also `dict`,
    # `collections.OrderedDict`, or `structure.Struct` when
    # elements has (name, value) tuples.
    return container_type(elements)