Skip to content

Commit 0a2c418

Browse files
TomAugspurgerjreback
authored andcommitted
REF: Store metadata in an attrs dict (#29062)
1 parent dd193d8 commit 0a2c418

File tree

4 files changed

+78
-18
lines changed

4 files changed

+78
-18
lines changed

doc/source/reference/frame.rst

+13
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,19 @@ Time series-related
274274
DataFrame.tz_convert
275275
DataFrame.tz_localize
276276

277+
.. _api.frame.metadata:
278+
279+
Metadata
280+
~~~~~~~~
281+
282+
:attr:`DataFrame.attrs` is a dictionary for storing global metadata for this DataFrame.
283+
284+
.. autosummary::
285+
:toctree: api/
286+
287+
DataFrame.attrs
288+
289+
277290
.. _api.dataframe.plotting:
278291

279292
Plotting

doc/source/reference/series.rst

+13
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,19 @@ Sparse-dtype specific methods and attributes are provided under the
531531
Series.sparse.to_coo
532532

533533

534+
.. _api.series.metadata:
535+
536+
Metadata
537+
~~~~~~~~
538+
539+
:attr:`Series.attrs` is a dictionary for storing global metadata for this Series.
540+
541+
.. autosummary::
542+
:toctree: api/
543+
544+
Series.attrs
545+
546+
534547
Plotting
535548
--------
536549
``Series.plot`` is both a callable method and a namespace attribute for

pandas/core/generic.py

+39-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import re
99
from textwrap import dedent
1010
from typing import (
11+
TYPE_CHECKING,
1112
Any,
1213
Callable,
1314
Dict,
1415
FrozenSet,
1516
Hashable,
1617
List,
18+
Mapping,
1719
Optional,
1820
Sequence,
1921
Set,
@@ -188,6 +190,12 @@ class NDFrame(PandasObject, SelectionMixin):
188190
_is_copy = None
189191
_data = None # type: BlockManager
190192

193+
if TYPE_CHECKING:
194+
# TODO(PY36): replace with _attrs : Dict[Hashable, Any]
195+
# We need the TYPE_CHECKING, because _attrs is not a class attribute
196+
# and Py35 doesn't support the new syntax.
197+
_attrs = {} # type: Dict[Hashable, Any]
198+
191199
# ----------------------------------------------------------------------
192200
# Constructors
193201

@@ -197,6 +205,7 @@ def __init__(
197205
axes: Optional[List[Index]] = None,
198206
copy: bool = False,
199207
dtype: Optional[Dtype] = None,
208+
attrs: Optional[Mapping[Hashable, Any]] = None,
200209
fastpath: bool = False,
201210
):
202211

@@ -213,6 +222,11 @@ def __init__(
213222
object.__setattr__(self, "_is_copy", None)
214223
object.__setattr__(self, "_data", data)
215224
object.__setattr__(self, "_item_cache", {})
225+
if attrs is None:
226+
attrs = {}
227+
else:
228+
attrs = dict(attrs)
229+
object.__setattr__(self, "_attrs", attrs)
216230

217231
def _init_mgr(self, mgr, axes=None, dtype=None, copy=False):
218232
""" passed a manager and a axes dict """
@@ -233,6 +247,19 @@ def _init_mgr(self, mgr, axes=None, dtype=None, copy=False):
233247

234248
# ----------------------------------------------------------------------
235249

250+
@property
251+
def attrs(self) -> Dict[Hashable, Any]:
252+
"""
253+
Dictionary of global attributes on this object.
254+
"""
255+
if self._attrs is None:
256+
self._attrs = {}
257+
return self._attrs
258+
259+
@attrs.setter
260+
def attrs(self, value: Mapping[Hashable, Any]) -> None:
261+
self._attrs = dict(value)
262+
236263
@property
237264
def is_copy(self):
238265
"""
@@ -2027,7 +2054,13 @@ def to_dense(self):
20272054

20282055
def __getstate__(self):
20292056
meta = {k: getattr(self, k, None) for k in self._metadata}
2030-
return dict(_data=self._data, _typ=self._typ, _metadata=self._metadata, **meta)
2057+
return dict(
2058+
_data=self._data,
2059+
_typ=self._typ,
2060+
_metadata=self._metadata,
2061+
attrs=self.attrs,
2062+
**meta
2063+
)
20312064

20322065
def __setstate__(self, state):
20332066

@@ -2036,6 +2069,8 @@ def __setstate__(self, state):
20362069
elif isinstance(state, dict):
20372070
typ = state.get("_typ")
20382071
if typ is not None:
2072+
attrs = state.get("_attrs", {})
2073+
object.__setattr__(self, "_attrs", attrs)
20392074

20402075
# set in the order of internal names
20412076
# to avoid definitional recursion
@@ -5202,6 +5237,9 @@ def __finalize__(self, other, method=None, **kwargs):
52025237
52035238
"""
52045239
if isinstance(other, NDFrame):
5240+
for name in other.attrs:
5241+
self.attrs[name] = other.attrs[name]
5242+
# For subclasses using _metadata.
52055243
for name in self._metadata:
52065244
object.__setattr__(self, name, getattr(other, name, None))
52075245
return self

pandas/core/series.py

+13-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from io import StringIO
66
from shutil import get_terminal_size
77
from textwrap import dedent
8-
from typing import Any, Callable
8+
from typing import Any, Callable, Hashable, List
99
import warnings
1010

1111
import numpy as np
@@ -29,7 +29,6 @@
2929
is_dict_like,
3030
is_extension_array_dtype,
3131
is_extension_type,
32-
is_hashable,
3332
is_integer,
3433
is_iterator,
3534
is_list_like,
@@ -45,6 +44,7 @@
4544
ABCSeries,
4645
ABCSparseArray,
4746
)
47+
from pandas.core.dtypes.inference import is_hashable
4848
from pandas.core.dtypes.missing import (
4949
isna,
5050
na_value_for_dtype,
@@ -173,7 +173,7 @@ class Series(base.IndexOpsMixin, generic.NDFrame):
173173
Copy input data.
174174
"""
175175

176-
_metadata = ["name"]
176+
_metadata = [] # type: List[str]
177177
_accessors = {"dt", "cat", "str", "sparse"}
178178
_deprecations = (
179179
base.IndexOpsMixin._deprecations
@@ -324,7 +324,6 @@ def __init__(
324324
data = SingleBlockManager(data, index, fastpath=True)
325325

326326
generic.NDFrame.__init__(self, data, fastpath=True)
327-
328327
self.name = name
329328
self._set_axis(0, index, fastpath=True)
330329

@@ -457,19 +456,6 @@ def _update_inplace(self, result, **kwargs):
457456
# we want to call the generic version and not the IndexOpsMixin
458457
return generic.NDFrame._update_inplace(self, result, **kwargs)
459458

460-
@property
461-
def name(self):
462-
"""
463-
Return name of the Series.
464-
"""
465-
return self._name
466-
467-
@name.setter
468-
def name(self, value):
469-
if value is not None and not is_hashable(value):
470-
raise TypeError("Series.name must be a hashable type")
471-
object.__setattr__(self, "_name", value)
472-
473459
# ndarray compatibility
474460
@property
475461
def dtype(self):
@@ -485,6 +471,16 @@ def dtypes(self):
485471
"""
486472
return self._data.dtype
487473

474+
@property
475+
def name(self) -> Hashable:
476+
return self.attrs.get("name", None)
477+
478+
@name.setter
479+
def name(self, value: Hashable) -> None:
480+
if not is_hashable(value):
481+
raise TypeError("Series.name must be a hashable type")
482+
self.attrs["name"] = value
483+
488484
@property
489485
def ftype(self):
490486
"""

0 commit comments

Comments
 (0)