1from __future__ import annotations
2
3from typing import TYPE_CHECKING
4import weakref
5
6if TYPE_CHECKING:
7 from pandas.core.generic import NDFrame
8
9
10class Flags:
11 """
12 Flags that apply to pandas objects.
13
14 Parameters
15 ----------
16 obj : Series or DataFrame
17 The object these flags are associated with.
18 allows_duplicate_labels : bool, default True
19 Whether to allow duplicate labels in this object. By default,
20 duplicate labels are permitted. Setting this to ``False`` will
21 cause an :class:`errors.DuplicateLabelError` to be raised when
22 `index` (or columns for DataFrame) is not unique, or any
23 subsequent operation on introduces duplicates.
24 See :ref:`duplicates.disallow` for more.
25
26 .. warning::
27
28 This is an experimental feature. Currently, many methods fail to
29 propagate the ``allows_duplicate_labels`` value. In future versions
30 it is expected that every method taking or returning one or more
31 DataFrame or Series objects will propagate ``allows_duplicate_labels``.
32
33 Examples
34 --------
35 Attributes can be set in two ways:
36
37 >>> df = pd.DataFrame()
38 >>> df.flags
39 <Flags(allows_duplicate_labels=True)>
40 >>> df.flags.allows_duplicate_labels = False
41 >>> df.flags
42 <Flags(allows_duplicate_labels=False)>
43
44 >>> df.flags['allows_duplicate_labels'] = True
45 >>> df.flags
46 <Flags(allows_duplicate_labels=True)>
47 """
48
49 _keys: set[str] = {"allows_duplicate_labels"}
50
51 def __init__(self, obj: NDFrame, *, allows_duplicate_labels: bool) -> None:
52 self._allows_duplicate_labels = allows_duplicate_labels
53 self._obj = weakref.ref(obj)
54
55 @property
56 def allows_duplicate_labels(self) -> bool:
57 """
58 Whether this object allows duplicate labels.
59
60 Setting ``allows_duplicate_labels=False`` ensures that the
61 index (and columns of a DataFrame) are unique. Most methods
62 that accept and return a Series or DataFrame will propagate
63 the value of ``allows_duplicate_labels``.
64
65 See :ref:`duplicates` for more.
66
67 See Also
68 --------
69 DataFrame.attrs : Set global metadata on this object.
70 DataFrame.set_flags : Set global flags on this object.
71
72 Examples
73 --------
74 >>> df = pd.DataFrame({"A": [1, 2]}, index=['a', 'a'])
75 >>> df.flags.allows_duplicate_labels
76 True
77 >>> df.flags.allows_duplicate_labels = False
78 Traceback (most recent call last):
79 ...
80 pandas.errors.DuplicateLabelError: Index has duplicates.
81 positions
82 label
83 a [0, 1]
84 """
85 return self._allows_duplicate_labels
86
87 @allows_duplicate_labels.setter
88 def allows_duplicate_labels(self, value: bool) -> None:
89 value = bool(value)
90 obj = self._obj()
91 if obj is None:
92 raise ValueError("This flag's object has been deleted.")
93
94 if not value:
95 for ax in obj.axes:
96 ax._maybe_check_unique()
97
98 self._allows_duplicate_labels = value
99
100 def __getitem__(self, key: str):
101 if key not in self._keys:
102 raise KeyError(key)
103
104 return getattr(self, key)
105
106 def __setitem__(self, key: str, value) -> None:
107 if key not in self._keys:
108 raise ValueError(f"Unknown flag {key}. Must be one of {self._keys}")
109 setattr(self, key, value)
110
111 def __repr__(self) -> str:
112 return f"<Flags(allows_duplicate_labels={self.allows_duplicate_labels})>"
113
114 def __eq__(self, other) -> bool:
115 if isinstance(other, type(self)):
116 return self.allows_duplicate_labels == other.allows_duplicate_labels
117 return False