Skip to content

Commit

Permalink
REF: Store metadata in an attrs dict (#29062)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAugspurger authored and jreback committed Oct 22, 2019
1 parent dd193d8 commit 0a2c418
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 18 deletions.
13 changes: 13 additions & 0 deletions doc/source/reference/frame.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,19 @@ Time series-related
DataFrame.tz_convert
DataFrame.tz_localize

.. _api.frame.metadata:

Metadata
~~~~~~~~

:attr:`DataFrame.attrs` is a dictionary for storing global metadata for this DataFrame.

.. autosummary::
:toctree: api/

DataFrame.attrs


.. _api.dataframe.plotting:

Plotting
Expand Down
13 changes: 13 additions & 0 deletions doc/source/reference/series.rst
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,19 @@ Sparse-dtype specific methods and attributes are provided under the
Series.sparse.to_coo


.. _api.series.metadata:

Metadata
~~~~~~~~

:attr:`Series.attrs` is a dictionary for storing global metadata for this Series.

.. autosummary::
:toctree: api/

Series.attrs


Plotting
--------
``Series.plot`` is both a callable method and a namespace attribute for
Expand Down
40 changes: 39 additions & 1 deletion pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import re
from textwrap import dedent
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
FrozenSet,
Hashable,
List,
Mapping,
Optional,
Sequence,
Set,
Expand Down Expand Up @@ -188,6 +190,12 @@ class NDFrame(PandasObject, SelectionMixin):
_is_copy = None
_data = None # type: BlockManager

if TYPE_CHECKING:
# TODO(PY36): replace with _attrs : Dict[Hashable, Any]
# We need the TYPE_CHECKING, because _attrs is not a class attribute
# and Py35 doesn't support the new syntax.
_attrs = {} # type: Dict[Hashable, Any]

# ----------------------------------------------------------------------
# Constructors

Expand All @@ -197,6 +205,7 @@ def __init__(
axes: Optional[List[Index]] = None,
copy: bool = False,
dtype: Optional[Dtype] = None,
attrs: Optional[Mapping[Hashable, Any]] = None,
fastpath: bool = False,
):

Expand All @@ -213,6 +222,11 @@ def __init__(
object.__setattr__(self, "_is_copy", None)
object.__setattr__(self, "_data", data)
object.__setattr__(self, "_item_cache", {})
if attrs is None:
attrs = {}
else:
attrs = dict(attrs)
object.__setattr__(self, "_attrs", attrs)

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

# ----------------------------------------------------------------------

@property
def attrs(self) -> Dict[Hashable, Any]:
"""
Dictionary of global attributes on this object.
"""
if self._attrs is None:
self._attrs = {}
return self._attrs

@attrs.setter
def attrs(self, value: Mapping[Hashable, Any]) -> None:
self._attrs = dict(value)

@property
def is_copy(self):
"""
Expand Down Expand Up @@ -2027,7 +2054,13 @@ def to_dense(self):

def __getstate__(self):
meta = {k: getattr(self, k, None) for k in self._metadata}
return dict(_data=self._data, _typ=self._typ, _metadata=self._metadata, **meta)
return dict(
_data=self._data,
_typ=self._typ,
_metadata=self._metadata,
attrs=self.attrs,
**meta
)

def __setstate__(self, state):

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

# set in the order of internal names
# to avoid definitional recursion
Expand Down Expand Up @@ -5202,6 +5237,9 @@ def __finalize__(self, other, method=None, **kwargs):
"""
if isinstance(other, NDFrame):
for name in other.attrs:
self.attrs[name] = other.attrs[name]
# For subclasses using _metadata.
for name in self._metadata:
object.__setattr__(self, name, getattr(other, name, None))
return self
Expand Down
30 changes: 13 additions & 17 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from io import StringIO
from shutil import get_terminal_size
from textwrap import dedent
from typing import Any, Callable
from typing import Any, Callable, Hashable, List
import warnings

import numpy as np
Expand All @@ -29,7 +29,6 @@
is_dict_like,
is_extension_array_dtype,
is_extension_type,
is_hashable,
is_integer,
is_iterator,
is_list_like,
Expand All @@ -45,6 +44,7 @@
ABCSeries,
ABCSparseArray,
)
from pandas.core.dtypes.inference import is_hashable
from pandas.core.dtypes.missing import (
isna,
na_value_for_dtype,
Expand Down Expand Up @@ -173,7 +173,7 @@ class Series(base.IndexOpsMixin, generic.NDFrame):
Copy input data.
"""

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

generic.NDFrame.__init__(self, data, fastpath=True)

self.name = name
self._set_axis(0, index, fastpath=True)

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

@property
def name(self):
"""
Return name of the Series.
"""
return self._name

@name.setter
def name(self, value):
if value is not None and not is_hashable(value):
raise TypeError("Series.name must be a hashable type")
object.__setattr__(self, "_name", value)

# ndarray compatibility
@property
def dtype(self):
Expand All @@ -485,6 +471,16 @@ def dtypes(self):
"""
return self._data.dtype

@property
def name(self) -> Hashable:
return self.attrs.get("name", None)

@name.setter
def name(self, value: Hashable) -> None:
if not is_hashable(value):
raise TypeError("Series.name must be a hashable type")
self.attrs["name"] = value

@property
def ftype(self):
"""
Expand Down

0 comments on commit 0a2c418

Please sign in to comment.