The .NET Tuple types Tuple<T1,…> and ValueTuple<T1,…> are not sequences — though the ITuple interface does enable item access by index without reflection — and ValueTuple<T1,…> instances are mutable, but .NET actually has two different immutable sequence types, ImmutableArray<T> and ImmutableList<T>, which are functionally similar but have different performance characteristics[1].
Along with the rest of the .NET immutable collection types[2], both are persistent data structures in the sense noted above.
In contrast, .NET tuple types are, as you say, lightweight, anonymous structures addressed by field. The ValueTuple<T1,…> types, in particular, are used in the underlying implementation of the C# tuple language feature[3].
AFAIK, Python has no built-in anonymous mutable structure types, though since type names in Python are basically only used for display purposes, you can easily create them at runtime, e.g.,
def anon(**kwargs):
class _:
__slots__ = tuple(kwargs.keys())
def __repr__(self):
return 'anon(' \
+ ', '.join((f'{i}={getattr(self, i)!r}' \
for i in self.__slots__)) \
+ ')'
def __eq__(self, other):
if not hasattr(other, '__slots__') \
or sorted(self.__slots__) \
!= sorted(other.__slots__):
return False
for i in self.__slots__:
if getattr(self, i) != getattr(other, i):
return False
return True
o = _()
for k,v in kwargs.items():
setattr(o, k, v)
return o
Note that this differs in two notable ways from C# tuples:
1. Each object created has a unique type, so
type(anon(x=1, y=2)) != type(anon(x=1, y=2))
More importantly, this means a distinct type object is created and stored for every call to anon, which could have significant performance implications at scale.
This could be easily fixed with a cache of already-created anonymous types (trading off slightly increased object-creation time, of course).
2. Equality in the above implementation is based on the equality of identically-named values rather than identically positioned values; in C#, we have
(x: 1, y: 2) != (y: 2, x: 1)
and
(x: 1, y: 2) == (y: 1, x: 2),
while in my implementation,
anon(x=1, y=2) == anon(y=2, x=1)
and
anon(x=1, y=2) != anon(y=1, x=2).
This was by choice, as it seems more intuitive; C# behavior is no more difficult to implement.
For read-only anonymous structure types, the Python standard library has namedtuple[4] (which, incidentally, bases value equality on position, not attribute name, so C#'s behavior is arguably more "Pythonic" than my own).
[1] https://learn.microsoft.com/en-us/dotnet/api/system.collecti...
[2] https://learn.microsoft.com/en-us/dotnet/api/system.collecti...
[3] https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
[4] https://docs.python.org/3/library/collections.html#collectio...