Skip to content

Commit e7e96d2

Browse files
committed
refactor!: real __dict__ instead of facade!
1 parent f1a09f3 commit e7e96d2

File tree

4 files changed

+75
-132
lines changed

4 files changed

+75
-132
lines changed

src/boost_histogram/_internal/axestuple.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def __getitem__(self, item):
9494
return self.__class__(result) if isinstance(result, tuple) else result
9595

9696
def __getattr__(self, attr):
97-
return self.__class__(s.__getattr__(attr) for s in self)
97+
return self.__class__(getattr(s, attr) for s in self)
9898

9999
def __setattr__(self, attr, values):
100100
return self.__class__(s.__setattr__(attr, v) for s, v in zip(self, values))

src/boost_histogram/_internal/axis.py

Lines changed: 67 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,9 @@
1111

1212
import copy
1313

14-
from typing import Dict, Any, TYPE_CHECKING, Optional
15-
1614
del absolute_import, division, print_function
1715

1816

19-
def _process_metadata_dict(metadata, __dict__):
20-
# type: (Optional[Any], Optional[Dict[str, Any]]) -> Dict[str, Any]
21-
"""
22-
Provide standardized handling for keywords related to metadata.
23-
"""
24-
if __dict__ is None:
25-
__dict__ = {}
26-
27-
if metadata is not None:
28-
if "metadata" in __dict__:
29-
raise KeyError("Cannot provide metadata by keyword and in __dict__")
30-
__dict__["metadata"] = metadata
31-
32-
return __dict__
33-
34-
3517
def _isstr(value):
3618
"""
3719
Check to see if this is a stringlike or a (nested) iterable of stringlikes
@@ -48,60 +30,56 @@ def _isstr(value):
4830
# Contains common methods and properties to all axes
4931
@set_module("boost_histogram.axis")
5032
class Axis(object):
51-
__slots__ = ("_ax",)
33+
__slots__ = ("_ax", "__dict__")
5234

53-
# Workaround for bug https://github.com/python/mypy/issues/6523 in mypy
54-
if TYPE_CHECKING:
55-
__dict__ = Dict[str, Any]()
56-
else:
35+
def __setattr__(self, attr, value):
36+
if attr == "__dict__":
37+
self._ax.metadata = value
38+
object.__setattr__(self, attr, value)
5739

58-
@property
59-
def __dict__(self):
60-
# type: () -> Dict[str, Any]
61-
return self._ax.metadata
40+
def __getattr__(self, attr):
41+
if attr == "metadata":
42+
return None
43+
raise AttributeError(
44+
"object {0} has not attribute {1}".format(self.__class__.__name__, attr)
45+
)
46+
47+
def __init__(self, ax, metadata, __dict__):
48+
"""
49+
ax: the C++ object
50+
metadata: the metadata keyword contents
51+
__dict__: the __dict__ keyword contents
52+
"""
6253

63-
@__dict__.deleter
64-
def __dict__(self):
65-
# type: (Dict[str, Any]) -> None
66-
self._ax.metadata = {}
54+
self._ax = ax
55+
56+
if __dict__ is not None and metadata is not None:
57+
raise KeyError(
58+
"Cannot provide metadata by keyword and __dict__, use __dict__ only"
59+
)
60+
elif __dict__ is not None:
61+
self.__dict__ = __dict__
62+
elif metadata is not None:
63+
self.__dict__["metadata"] = metadata
64+
65+
self._ax.metadata = self.__dict__
66+
assert self.__dict__ is self._ax.metadata
6767

68-
# Required for Python 2 + __dict__
6968
def __setstate__(self, state):
7069
self._ax = state["_ax"]
70+
self.__dict__ = self._ax.metadata
71+
assert self.__dict__ is self._ax.metadata
7172

7273
def __getstate__(self):
7374
return {"_ax": self._ax}
7475

75-
def __getattr__(self, item):
76-
if item == "_ax":
77-
return Axis.__dict__[item].__get__(self)
78-
elif item in self._ax.metadata:
79-
return self._ax.metadata[item]
80-
elif item == "metadata":
81-
return None
82-
else:
83-
msg = "'{}' object has no attribute '{}' in {}".format(
84-
type(self).__name__, item, set(self._ax.metadata)
85-
)
86-
raise AttributeError(msg)
87-
88-
def __setattr__(self, item, value):
89-
if item == "_ax":
90-
Axis.__dict__[item].__set__(self, value)
91-
elif item == "__dict__":
92-
self._ax.metadata = value
93-
else:
94-
self._ax.metadata[item] = value
95-
9676
def __copy__(self):
9777
other = self.__class__.__new__(self.__class__)
9878
other._ax = copy.copy(self._ax)
79+
other.__dict__ = other._ax.metadata
80+
assert other.__dict__ is other._ax.metadata
9981
return other
10082

101-
def __dir__(self):
102-
metadata = list(self._ax.metadata)
103-
return sorted(dir(type(self)) + metadata)
104-
10583
def index(self, value):
10684
"""
10785
Return the fractional index(es) given a value (or values) on the axis.
@@ -142,6 +120,8 @@ def __ne__(self, other):
142120
def _convert_cpp(cls, cpp_object):
143121
nice_ax = cls.__new__(cls)
144122
nice_ax._ax = cpp_object
123+
nice_ax.__dict__ = cpp_object.metadata
124+
assert nice_ax._ax.metadata == nice_ax.__dict__
145125
return nice_ax
146126

147127
def __len__(self):
@@ -304,9 +284,6 @@ def __init__(self, bins, start, stop, **kwargs):
304284
The full metadata dictionary
305285
"""
306286

307-
# Inheriting an axis and forgetting to add __slots__ should be an error
308-
assert not hasattr(self, "__weakref__"), "Axis subclasses must have __slots__!"
309-
310287
with KWArgs(kwargs) as k:
311288
metadata = k.optional("metadata")
312289
transform = k.optional("transform")
@@ -315,8 +292,6 @@ def __init__(self, bins, start, stop, **kwargs):
315292
underflow=True, overflow=True, growth=False, circular=False
316293
)
317294

318-
__dict__ = _process_metadata_dict(metadata, __dict__)
319-
320295
if transform is not None:
321296
if options != {"underflow", "overflow"}:
322297
raise KeyError("Transform supplied, cannot change other options")
@@ -327,29 +302,29 @@ def __init__(self, bins, start, stop, **kwargs):
327302
):
328303
raise TypeError("You must pass an instance, use {}()".format(transform))
329304

330-
self._ax = transform._produce(bins, start, stop)
305+
ax = transform._produce(bins, start, stop)
331306

332307
elif options == {"growth", "underflow", "overflow"}:
333-
self._ax = ca.regular_uoflow_growth(bins, start, stop)
308+
ax = ca.regular_uoflow_growth(bins, start, stop)
334309
elif options == {"underflow", "overflow"}:
335-
self._ax = ca.regular_uoflow(bins, start, stop)
310+
ax = ca.regular_uoflow(bins, start, stop)
336311
elif options == {"underflow"}:
337-
self._ax = ca.regular_uflow(bins, start, stop)
312+
ax = ca.regular_uflow(bins, start, stop)
338313
elif options == {"overflow"}:
339-
self._ax = ca.regular_oflow(bins, start, stop)
314+
ax = ca.regular_oflow(bins, start, stop)
340315
elif options == {"circular", "underflow", "overflow"} or options == {
341316
"circular",
342317
"overflow",
343318
}:
344319
# growth=True, underflow=False is also correct
345-
self._ax = ca.regular_circular(bins, start, stop)
320+
ax = ca.regular_circular(bins, start, stop)
346321

347322
elif options == set():
348-
self._ax = ca.regular_none(bins, start, stop)
323+
ax = ca.regular_none(bins, start, stop)
349324
else:
350325
raise KeyError("Unsupported collection of options")
351326

352-
self._ax.metadata = __dict__
327+
super(Regular, self).__init__(ax, metadata, __dict__)
353328

354329
def _repr_args(self):
355330
"Return inner part of signature for use in repr"
@@ -415,38 +390,33 @@ def __init__(self, edges, **kwargs):
415390
The full metadata dictionary
416391
"""
417392

418-
# Inheriting an axis and forgetting to add __slots__ should be an error
419-
assert not hasattr(self, "__weakref__"), "Axis subclasses must have __slots__!"
420-
421393
with KWArgs(kwargs) as k:
422394
metadata = k.optional("metadata")
423395
__dict__ = k.optional("__dict__")
424396
options = k.options(
425397
underflow=True, overflow=True, circular=False, growth=False
426398
)
427399

428-
__dict__ = _process_metadata_dict(metadata, __dict__)
429-
430400
if options == {"growth", "underflow", "overflow"}:
431-
self._ax = ca.variable_uoflow_growth(edges)
401+
ax = ca.variable_uoflow_growth(edges)
432402
elif options == {"underflow", "overflow"}:
433-
self._ax = ca.variable_uoflow(edges)
403+
ax = ca.variable_uoflow(edges)
434404
elif options == {"underflow"}:
435-
self._ax = ca.variable_uflow(edges)
405+
ax = ca.variable_uflow(edges)
436406
elif options == {"overflow"}:
437-
self._ax = ca.variable_oflow(edges)
407+
ax = ca.variable_oflow(edges)
438408
elif options == {"circular", "underflow", "overflow",} or options == {
439409
"circular",
440410
"overflow",
441411
}:
442412
# growth=True, underflow=False is also correct
443-
self._ax = ca.variable_circular(edges)
413+
ax = ca.variable_circular(edges)
444414
elif options == set():
445-
self._ax = ca.variable_none(edges)
415+
ax = ca.variable_none(edges)
446416
else:
447417
raise KeyError("Unsupported collection of options")
448418

449-
self._ax.metadata = __dict__
419+
super(Variable, self).__init__(ax, metadata, __dict__)
450420

451421
def _repr_args(self):
452422
"Return inner part of signature for use in repr"
@@ -500,36 +470,31 @@ def __init__(self, start, stop, **kwargs):
500470
The full metadata dictionary
501471
"""
502472

503-
# Inheriting an axis and forgetting to add __slots__ should be an error
504-
assert not hasattr(self, "__weakref__"), "Axis subclasses must have __slots__!"
505-
506473
with KWArgs(kwargs) as k:
507474
metadata = k.optional("metadata")
508475
__dict__ = k.optional("__dict__")
509476
options = k.options(
510477
underflow=True, overflow=True, circular=False, growth=False
511478
)
512479

513-
__dict__ = _process_metadata_dict(metadata, __dict__)
514-
515480
# underflow and overflow settings are ignored, integers are always
516481
# finite and thus cannot end up in a flow bin when growth is on
517482
if "growth" in options and "circular" not in options:
518-
self._ax = ca.integer_growth(start, stop)
483+
ax = ca.integer_growth(start, stop)
519484
elif options == {"underflow", "overflow"}:
520-
self._ax = ca.integer_uoflow(start, stop)
485+
ax = ca.integer_uoflow(start, stop)
521486
elif options == {"underflow"}:
522-
self._ax = ca.integer_uflow(start, stop)
487+
ax = ca.integer_uflow(start, stop)
523488
elif options == {"overflow"}:
524-
self._ax = ca.integer_oflow(start, stop)
489+
ax = ca.integer_oflow(start, stop)
525490
elif "circular" in options and "growth" not in options:
526-
self._ax = ca.integer_circular(start, stop)
491+
ax = ca.integer_circular(start, stop)
527492
elif options == set():
528-
self._ax = ca.integer_none(start, stop)
493+
ax = ca.integer_none(start, stop)
529494
else:
530495
raise KeyError("Unsupported collection of options")
531496

532-
self._ax.metadata = __dict__
497+
super(Integer, self).__init__(ax, metadata, __dict__)
533498

534499
def _repr_args(self):
535500
"Return inner part of signature for use in repr"
@@ -586,27 +551,22 @@ def __init__(self, categories, **kwargs):
586551
The full metadata dictionary
587552
"""
588553

589-
# Inheriting an axis and forgetting to add __slots__ should be an error
590-
assert not hasattr(self, "__weakref__"), "Axis subclasses must have __slots__!"
591-
592554
with KWArgs(kwargs) as k:
593555
metadata = k.optional("metadata")
594556
__dict__ = k.optional("__dict__")
595557
options = k.options(growth=False)
596558

597-
__dict__ = _process_metadata_dict(metadata, __dict__)
598-
599559
# henryiii: We currently expand "abc" to "a", "b", "c" - some
600560
# Python interfaces protect against that
601561

602562
if options == {"growth"}:
603-
self._ax = ca.category_str_growth(tuple(categories))
563+
ax = ca.category_str_growth(tuple(categories))
604564
elif options == set():
605-
self._ax = ca.category_str(tuple(categories))
565+
ax = ca.category_str(tuple(categories))
606566
else:
607567
raise KeyError("Unsupported collection of options")
608568

609-
self._ax.metadata = __dict__
569+
super(StrCategory, self).__init__(ax, metadata, __dict__)
610570

611571
def index(self, value):
612572
"""
@@ -655,24 +615,19 @@ def __init__(self, categories, **kwargs):
655615
The full metadata dictionary
656616
"""
657617

658-
# Inheriting an axis and forgetting to add __slots__ should be an error
659-
assert not hasattr(self, "__weakref__"), "Axis subclasses must have __slots__!"
660-
661618
with KWArgs(kwargs) as k:
662619
metadata = k.optional("metadata")
663620
__dict__ = k.optional("__dict__")
664621
options = k.options(growth=False)
665622

666-
__dict__ = _process_metadata_dict(metadata, __dict__)
667-
668623
if options == {"growth"}:
669-
self._ax = ca.category_int_growth(tuple(categories))
624+
ax = ca.category_int_growth(tuple(categories))
670625
elif options == set():
671-
self._ax = ca.category_int(tuple(categories))
626+
ax = ca.category_int(tuple(categories))
672627
else:
673628
raise KeyError("Unsupported collection of options")
674629

675-
self._ax.metadata = __dict__
630+
super(IntCategory, self).__init__(ax, metadata, __dict__)
676631

677632
def _repr_args(self):
678633
"Return inner part of signature for use in repr"
@@ -700,17 +655,13 @@ def __init__(self, **kwargs):
700655
The full metadata dictionary
701656
"""
702657

703-
# Inheriting an axis and forgetting to add __slots__ should be an error
704-
assert not hasattr(self, "__weakref__"), "Axis subclasses must have __slots__!"
705-
706658
with KWArgs(kwargs) as k:
707659
metadata = k.optional("metadata")
708660
__dict__ = k.optional("__dict__")
709661

710-
__dict__ = _process_metadata_dict(metadata, __dict__)
662+
ax = ca.boolean()
711663

712-
self._ax = ca.boolean()
713-
self._ax.metadata = __dict__
664+
super(Boolean, self).__init__(ax, metadata, __dict__)
714665

715666
def _repr_args(self):
716667
"Return inner part of signature for use in repr"

0 commit comments

Comments
 (0)