# Licensed under a 3-clause BSD style license - see LICENSE.rst
import collections
from collections import OrderedDict
from operator import index as operator_index
import numpy as np
from astropy.utils.compat import COPY_IF_NEEDED
[docs]
class Row:
"""A class to represent one row of a Table object.
A Row object is returned when a Table object is indexed with an integer
or when iterating over a table::
>>> from astropy.table import Table
>>> table = Table([(1, 2), (3, 4)], names=('a', 'b'),
... dtype=('int32', 'int32'))
>>> row = table[1]
>>> row
<Row index=1>
a b
int32 int32
----- -----
2 4
>>> row['a']
np.int32(2)
>>> row[1]
np.int32(4)
"""
def __init__(self, table, index):
# Ensure that the row index is a valid index (int)
index = operator_index(index)
n = len(table)
if index < -n or index >= n:
raise IndexError(
f"index {index} out of range for table with length {len(table)}"
)
# Finally, ensure the index is positive [#8422] and set Row attributes
self._index = index % n
self._table = table
def __getitem__(self, item):
try:
# Try the most common use case of accessing a single column in the Row.
# Bypass the TableColumns __getitem__ since that does more testing
# and allows a list of tuple or str, which is not the right thing here.
out = OrderedDict.__getitem__(self._table.columns, item)[self._index]
except (KeyError, TypeError):
if self._table._is_list_or_tuple_of_str(item):
cols = [self._table[name] for name in item]
out = self._table.__class__(cols, copy=False)[self._index]
elif isinstance(item, slice):
# https://github.com/astropy/astropy/issues/14007
out = tuple(self.values())[item]
else:
# This is only to raise an exception
out = self._table.columns[item][self._index]
return out
def __setitem__(self, item, val):
if self._table._is_list_or_tuple_of_str(item):
self._table._set_row(self._index, colnames=item, vals=val)
else:
self._table.columns[item][self._index] = val
def _ipython_key_completions_(self):
return self.colnames
def __eq__(self, other):
if self._table.masked:
# Sent bug report to numpy-discussion group on 2012-Oct-21, subject:
# "Comparing rows in a structured masked array raises exception"
# No response, so this is still unresolved.
raise ValueError(
"Unable to compare rows for masked table due to numpy.ma bug"
)
return self.as_void() == other
def __ne__(self, other):
if self._table.masked:
raise ValueError(
"Unable to compare rows for masked table due to numpy.ma bug"
)
return self.as_void() != other
def __array__(self, dtype=None, copy=COPY_IF_NEEDED):
"""Support converting Row to np.array via np.array(table).
Coercion to a different dtype via np.array(table, dtype) is not
supported and will raise a ValueError.
If the parent table is masked then the mask information is dropped.
"""
if dtype is not None:
raise ValueError("Datatype coercion is not allowed")
return np.array(self.as_void(), copy=copy)
def __len__(self):
return len(self._table.columns)
def __iter__(self):
index = self._index
for col in self._table.columns.values():
yield col[index]
[docs]
def get(self, key, default=None, /):
"""Return the value for key if key is in the columns, else default.
Parameters
----------
key : `str`, positional-only
The name of the column to look for.
default : `object`, optional, positional-only
The value to return if the ``key`` is not among the columns.
Returns
-------
`object`
The value in the ``key`` column of the row if present,
``default`` otherwise.
Examples
--------
>>> from astropy.table import Table
>>> t = Table({"a": [2., 3., 5.], "b": [7., 11., 13.]})
>>> t[0].get("a")
np.float64(2.0)
>>> t[1].get("b", 0.)
np.float64(11.0)
>>> t[2].get("c", 0.)
0.0
"""
return self[key] if key in self._table.columns else default
[docs]
def keys(self):
return self._table.columns.keys()
[docs]
def values(self):
return self.__iter__()
@property
def table(self):
return self._table
@property
def index(self):
return self._index
[docs]
def as_void(self):
"""
Returns a *read-only* copy of the row values in the form of np.void or
np.ma.mvoid objects. This corresponds to the object types returned for
row indexing of a pure numpy structured array or masked array. This
method is slow and its use is discouraged when possible.
Returns
-------
void_row : ``numpy.void`` or ``numpy.ma.mvoid``
Copy of row values.
``numpy.void`` if unmasked, ``numpy.ma.mvoid`` else.
"""
index = self._index
cols = self._table.columns.values()
vals = tuple(np.asarray(col)[index] for col in cols)
if self._table.masked:
mask = tuple(
col.mask[index] if hasattr(col, "mask") else False for col in cols
)
void_row = np.ma.array([vals], mask=[mask], dtype=self.dtype)[0]
else:
void_row = np.array([vals], dtype=self.dtype)[0]
return void_row
@property
def meta(self):
return self._table.meta
@property
def columns(self):
return self._table.columns
@property
def colnames(self):
return self._table.colnames
@property
def dtype(self):
return self._table.dtype
def _base_repr_(self, html=False):
"""
Display row as a single-line table but with appropriate header line.
"""
index = self.index if (self.index >= 0) else self.index + len(self._table)
table = self._table[index : index + 1]
descr_vals = [self.__class__.__name__, f"index={self.index}"]
if table.masked:
descr_vals.append("masked=True")
return table._base_repr_(
html, descr_vals, max_width=-1, tableid=f"table{id(self._table)}"
)
def _repr_html_(self):
return self._base_repr_(html=True)
def __repr__(self):
return self._base_repr_(html=False)
def __str__(self):
index = self.index if (self.index >= 0) else self.index + len(self._table)
return "\n".join(self.table[index : index + 1].pformat(max_width=-1))
def __bytes__(self):
return str(self).encode("utf-8")
collections.abc.Sequence.register(Row)