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)