Skip to content

Commit 930aa9d

Browse files
committed
Squashed commit of the following:
commit 67a3263 Author: Tom Augspurger <[email protected]> Date: Fri Oct 18 08:05:04 2019 -0500 fixup name commit e6183cd Author: Tom Augspurger <[email protected]> Date: Fri Oct 18 07:05:33 2019 -0500 fixup Index.name commit d1826bb Author: Tom Augspurger <[email protected]> Date: Thu Oct 17 13:45:30 2019 -0500 REF: Store metadata in attrs dict This aids in the implementation of pandas-dev#28394. Over there, I'm having issues with using `NDFrame.__finalize__` to copy attributes, in part because getattribute on NDFrame is so complicated. This simplifies this because we only need to look in NDFrame.attrs, which is just a plain dictionary. Aside from the addition of a public NDFrame.attrs dictionary, there aren't any user-facing API changes.
1 parent 3915847 commit 930aa9d

File tree

4 files changed

+77
-17
lines changed

4 files changed

+77
-17
lines changed

doc/source/reference/frame.rst

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

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

280293
Plotting

doc/source/reference/series.rst

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

534534

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

pandas/core/generic.py

+38-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,
@@ -189,6 +191,12 @@ class NDFrame(PandasObject, SelectionMixin):
189191
_is_copy = None
190192
_data = None # type: BlockManager
191193

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

@@ -199,6 +207,7 @@ def __init__(
199207
copy: bool = False,
200208
dtype: Optional[Dtype] = None,
201209
allow_duplicate_labels: bool = True,
210+
attrs: Optional[Mapping[Hashable, Any]] = None,
202211
fastpath: bool = False,
203212
):
204213

@@ -216,6 +225,11 @@ def __init__(
216225
object.__setattr__(self, "_data", data)
217226
object.__setattr__(self, "_item_cache", {})
218227
object.__setattr__(self, "allows_duplicate_labels", allow_duplicate_labels)
228+
if attrs is None:
229+
attrs = {}
230+
else:
231+
attrs = dict(attrs)
232+
object.__setattr__(self, "_attrs", attrs)
219233

220234
def _init_mgr(self, mgr, axes=None, dtype=None, copy=False):
221235
""" passed a manager and a axes dict """
@@ -252,6 +266,18 @@ def allows_duplicate_labels(self, value: bool):
252266

253267
self._allows_duplicate_labels = value
254268

269+
def attrs(self) -> Dict[Hashable, Any]:
270+
"""
271+
Dictionary of global attributes on this object.
272+
"""
273+
if self._attrs is None:
274+
self._attrs = {}
275+
return self._attrs
276+
277+
@attrs.setter
278+
def attrs(self, value: Mapping[Hashable, Any]) -> None:
279+
self._attrs = dict(value)
280+
255281
@property
256282
def is_copy(self):
257283
"""
@@ -2048,7 +2074,13 @@ def to_dense(self):
20482074

20492075
def __getstate__(self):
20502076
meta = {k: getattr(self, k, None) for k in self._metadata}
2051-
return dict(_data=self._data, _typ=self._typ, _metadata=self._metadata, **meta)
2077+
return dict(
2078+
_data=self._data,
2079+
_typ=self._typ,
2080+
_metadata=self._metadata,
2081+
attrs=self.attrs,
2082+
**meta
2083+
)
20522084

20532085
def __setstate__(self, state):
20542086

@@ -2057,6 +2089,8 @@ def __setstate__(self, state):
20572089
elif isinstance(state, dict):
20582090
typ = state.get("_typ")
20592091
if typ is not None:
2092+
attrs = state.get("_attrs", {})
2093+
object.__setattr__(self, "_attrs", attrs)
20602094

20612095
# set in the order of internal names
20622096
# to avoid definitional recursion
@@ -5255,6 +5289,9 @@ def finalize_name(objs):
52555289

52565290
# import pdb; pdb.set_trace()
52575291
if isinstance(other, NDFrame):
5292+
for name in other.attrs:
5293+
self.attrs[name] = other.attrs[name]
5294+
# For subclasses using _metadata.
52585295
for name in self._metadata:
52595296
if name == "name" and getattr(other, "ndim", None) == 1:
52605297
# Calling hasattr(other, 'name') is bad for DataFrames with

pandas/core/series.py

+13-16
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,
@@ -179,7 +179,7 @@ class Series(base.IndexOpsMixin, generic.NDFrame):
179179
introduces duplicates. See :ref:`duplictes.disallow` for more.
180180
"""
181181

182-
_metadata = ["name", "allows_duplicate_labels"]
182+
_metadata = [] # type: List[str]
183183
_accessors = {"dt", "cat", "str", "sparse"}
184184
_deprecations = (
185185
base.IndexOpsMixin._deprecations
@@ -472,19 +472,6 @@ def _update_inplace(self, result, **kwargs):
472472
# we want to call the generic version and not the IndexOpsMixin
473473
return generic.NDFrame._update_inplace(self, result, **kwargs)
474474

475-
@property
476-
def name(self):
477-
"""
478-
Return name of the Series.
479-
"""
480-
return self._name
481-
482-
@name.setter
483-
def name(self, value):
484-
if value is not None and not is_hashable(value):
485-
raise TypeError("Series.name must be a hashable type")
486-
object.__setattr__(self, "_name", value)
487-
488475
# ndarray compatibility
489476
@property
490477
def dtype(self):
@@ -500,6 +487,16 @@ def dtypes(self):
500487
"""
501488
return self._data.dtype
502489

490+
@property
491+
def name(self) -> Hashable:
492+
return self.attrs.get("name", None)
493+
494+
@name.setter
495+
def name(self, value: Hashable) -> None:
496+
if not is_hashable(value):
497+
raise TypeError("Series.name must be a hashable type")
498+
self.attrs["name"] = value
499+
503500
@property
504501
def ftype(self):
505502
"""

0 commit comments

Comments
 (0)