Skip to content

Commit d1826bb

Browse files
committed
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 509eb14 commit d1826bb

File tree

5 files changed

+47
-20
lines changed

5 files changed

+47
-20
lines changed

doc/source/whatsnew/v1.0.0.rst

-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ Deprecations
204204
- ``Index.set_value`` has been deprecated. For a given index ``idx``, array ``arr``,
205205
value in ``idx`` of ``idx_val`` and a new value of ``val``, ``idx.set_value(arr, idx_val, val)``
206206
is equivalent to ``arr[idx.get_loc(idx_val)] = val``, which should be used instead (:issue:`28621`).
207-
-
208207

209208
.. _whatsnew_1000.prior_deprecations:
210209

pandas/core/base.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import builtins
55
from collections import OrderedDict
66
import textwrap
7-
from typing import Dict, FrozenSet, Optional
7+
from typing import Dict, FrozenSet, Hashable, Optional
88
import warnings
99

1010
import numpy as np
@@ -30,6 +30,7 @@
3030
is_timedelta64_ns_dtype,
3131
)
3232
from pandas.core.dtypes.generic import ABCDataFrame, ABCIndexClass, ABCSeries
33+
from pandas.core.dtypes.inference import is_hashable
3334
from pandas.core.dtypes.missing import isna
3435

3536
from pandas.core import algorithms, common as com
@@ -663,6 +664,16 @@ class IndexOpsMixin:
663664
]
664665
) # type: FrozenSet[str]
665666

667+
@property
668+
def name(self) -> Optional[Hashable]:
669+
return self.attrs.get("name", None)
670+
671+
@name.setter
672+
def name(self, value: Hashable) -> None:
673+
if not is_hashable(value):
674+
raise TypeError("Series.name must be a hashable type")
675+
self.attrs["name"] = value
676+
666677
def transpose(self, *args, **kwargs):
667678
"""
668679
Return the transpose, which is by definition self.

pandas/core/generic.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
FrozenSet,
1515
Hashable,
1616
List,
17+
Mapping,
1718
Optional,
1819
Sequence,
1920
Set,
@@ -197,6 +198,7 @@ def __init__(
197198
axes: Optional[List[Index]] = None,
198199
copy: bool = False,
199200
dtype: Optional[Dtype] = None,
201+
attrs: Mapping[Hashable, Any] = None,
200202
fastpath: bool = False,
201203
):
202204

@@ -213,6 +215,11 @@ def __init__(
213215
object.__setattr__(self, "_is_copy", None)
214216
object.__setattr__(self, "_data", data)
215217
object.__setattr__(self, "_item_cache", {})
218+
if attrs is None:
219+
attrs = {}
220+
else:
221+
attrs = dict(attrs)
222+
object.__setattr__(self, "_attrs", attrs)
216223

217224
def _init_mgr(self, mgr, axes=None, dtype=None, copy=False):
218225
""" passed a manager and a axes dict """
@@ -233,6 +240,19 @@ def _init_mgr(self, mgr, axes=None, dtype=None, copy=False):
233240

234241
# ----------------------------------------------------------------------
235242

243+
@property
244+
def attrs(self):
245+
"""
246+
Dictionary of global attributes on this object.
247+
"""
248+
if self._attrs is None:
249+
self._attrs = {}
250+
return self._attrs
251+
252+
@attrs.setter
253+
def attrs(self, value: Mapping[Hashable, Any]) -> None:
254+
self._attrs = dict(value)
255+
236256
@property
237257
def is_copy(self):
238258
"""
@@ -2029,7 +2049,13 @@ def to_dense(self):
20292049

20302050
def __getstate__(self):
20312051
meta = {k: getattr(self, k, None) for k in self._metadata}
2032-
return dict(_data=self._data, _typ=self._typ, _metadata=self._metadata, **meta)
2052+
return dict(
2053+
_data=self._data,
2054+
_typ=self._typ,
2055+
_metadata=self._metadata,
2056+
attrs=self.attrs,
2057+
**meta
2058+
)
20332059

20342060
def __setstate__(self, state):
20352061

@@ -2038,6 +2064,8 @@ def __setstate__(self, state):
20382064
elif isinstance(state, dict):
20392065
typ = state.get("_typ")
20402066
if typ is not None:
2067+
attrs = state.get("_attrs", {})
2068+
object.__setattr__(self, "_attrs", attrs)
20412069

20422070
# set in the order of internal names
20432071
# to avoid definitional recursion
@@ -5213,6 +5241,9 @@ def __finalize__(self, other, method=None, **kwargs):
52135241
52145242
"""
52155243
if isinstance(other, NDFrame):
5244+
for name in other.attrs:
5245+
self.attrs[name] = other.attrs[name]
5246+
# For subclasses using _metadata.
52165247
for name in self._metadata:
52175248
object.__setattr__(self, name, getattr(other, name, None))
52185249
return self

pandas/core/indexes/base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime
22
import operator
33
from textwrap import dedent
4-
from typing import FrozenSet, Union
4+
from typing import Any, FrozenSet, Hashable, Mapping, Union
55
import warnings
66

77
import numpy as np
@@ -266,6 +266,7 @@ def __new__(
266266
name=None,
267267
fastpath=None,
268268
tupleize_cols=True,
269+
attrs: Mapping[Hashable, Any] = None,
269270
**kwargs
270271
) -> "Index":
271272

pandas/core/series.py

+1-16
Original file line numberDiff line numberDiff line change
@@ -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,
@@ -173,7 +172,7 @@ class Series(base.IndexOpsMixin, generic.NDFrame):
173172
Copy input data.
174173
"""
175174

176-
_metadata = ["name"]
175+
_metadata = []
177176
_accessors = {"dt", "cat", "str", "sparse"}
178177
_deprecations = (
179178
base.IndexOpsMixin._deprecations
@@ -324,7 +323,6 @@ def __init__(
324323
data = SingleBlockManager(data, index, fastpath=True)
325324

326325
generic.NDFrame.__init__(self, data, fastpath=True)
327-
328326
self.name = name
329327
self._set_axis(0, index, fastpath=True)
330328

@@ -457,19 +455,6 @@ def _update_inplace(self, result, **kwargs):
457455
# we want to call the generic version and not the IndexOpsMixin
458456
return generic.NDFrame._update_inplace(self, result, **kwargs)
459457

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-
473458
# ndarray compatibility
474459
@property
475460
def dtype(self):

0 commit comments

Comments
 (0)