@@ -295,15 +295,14 @@ def __repr__(self):
295
295
with warnings .catch_warnings ():
296
296
warnings .filterwarnings ("ignore" , category = DeprecationWarning )
297
297
298
- # Objects with custom __eq__ that returns non-bool are dropped
299
- # even if they're "equal" because the comparison result is ambiguous
298
+ # With truthiness: objects returning [True] are kept (truthy)
300
299
actual = xr .merge ([ds4 , ds5 ], combine_attrs = "drop_conflicts" )
301
- assert "custom" not in actual .attrs # Dropped due to non-bool comparison
300
+ assert "custom" in actual .attrs # Kept - [True] is truthy
302
301
assert actual .attrs ["x" ] == 1
303
302
304
- # Objects with different values also get dropped
303
+ # Objects with different values: equivalent returns False (bool), dropped
305
304
actual = xr .merge ([ds4 , ds6 ], combine_attrs = "drop_conflicts" )
306
- assert "custom" not in actual .attrs # Dropped due to non-bool comparison
305
+ assert "custom" not in actual .attrs # Dropped - different values
307
306
assert actual .attrs ["x" ] == 1
308
307
assert actual .attrs ["y" ] == 2
309
308
@@ -427,10 +426,10 @@ def test_merge_attrs_drop_conflicts_pathological_cases(self):
427
426
assert "dataset_attr" not in actual .attrs # Dropped due to TypeError
428
427
assert actual .attrs ["scalar" ] == 42
429
428
430
- # Even identical datasets cause TypeError in equivalent() and get dropped
431
- # because equivalent() tries to convert them to numpy arrays
429
+ # With truthiness: identical datasets are kept
430
+ # The comparison returns a truthy Dataset, so they're treated as equal
432
431
actual = xr .merge ([ds4 , ds6 ], combine_attrs = "drop_conflicts" )
433
- assert "dataset_attr" not in actual .attrs # Dropped due to TypeError
432
+ assert "dataset_attr" in actual .attrs # Kept with truthiness approach
434
433
assert actual .attrs ["other" ] == 99
435
434
436
435
# Test 3: Pandas Series (raises ValueError due to ambiguous truth value)
@@ -458,6 +457,62 @@ def test_merge_attrs_drop_conflicts_pathological_cases(self):
458
457
assert "series" not in actual .attrs # Dropped due to ValueError
459
458
assert actual .attrs ["value" ] == "a"
460
459
460
+ def test_merge_attrs_drop_conflicts_truthiness_edge_cases (self ):
461
+ """Test edge cases demonstrating the truthiness tradeoff.
462
+
463
+ We deliberately use truthiness for consistency with Python's `if a == b:`
464
+ behavior. This test documents the implications of this design choice
465
+ with objects that have non-standard __eq__ methods.
466
+ """
467
+
468
+ # Case 1: Objects whose __eq__ returns truthy non-booleans
469
+ # These are kept because we respect truthiness
470
+ class ReturnsString :
471
+ def __init__ (self , value ):
472
+ self .value = value
473
+
474
+ def __eq__ (self , other ):
475
+ # Always returns a string (truthy if non-empty)
476
+ return "comparison result"
477
+
478
+ obj1 = ReturnsString ("A" )
479
+ obj2 = ReturnsString ("B" ) # Different object
480
+
481
+ ds1 = xr .Dataset (attrs = {"obj" : obj1 })
482
+ ds2 = xr .Dataset (attrs = {"obj" : obj2 })
483
+
484
+ actual = xr .merge ([ds1 , ds2 ], combine_attrs = "drop_conflicts" )
485
+
486
+ # Truthiness behavior: keeps attribute because "comparison result" is truthy
487
+ # This is the expected behavior when respecting truthiness
488
+ assert "obj" in actual .attrs
489
+
490
+ # Case 2: Objects whose __eq__ returns falsy non-booleans
491
+ # These are dropped because we respect truthiness
492
+ class ReturnsZero :
493
+ def __init__ (self , value ):
494
+ self .value = value
495
+
496
+ def __eq__ (self , other ):
497
+ # Always returns 0 (falsy) even if values match
498
+ return 0
499
+
500
+ obj3 = ReturnsZero ("same" )
501
+ obj4 = ReturnsZero ("same" ) # Different object, same value
502
+
503
+ ds3 = xr .Dataset (attrs = {"zero" : obj3 })
504
+ ds4 = xr .Dataset (attrs = {"zero" : obj4 })
505
+
506
+ actual = xr .merge ([ds3 , ds4 ], combine_attrs = "drop_conflicts" )
507
+
508
+ # Truthiness behavior: drops attribute because 0 is falsy
509
+ # This is the expected behavior when respecting truthiness
510
+ assert "zero" not in actual .attrs
511
+
512
+ # Note: These edge cases demonstrate the tradeoff of using truthiness.
513
+ # Well-behaved __eq__ methods return booleans and work correctly.
514
+ # We accept these edge cases for consistency with Python's standard behavior.
515
+
461
516
def test_merge_attrs_no_conflicts_compat_minimal (self ):
462
517
"""make sure compat="minimal" does not silence errors"""
463
518
ds1 = xr .Dataset ({"a" : ("x" , [], {"a" : 0 })})
0 commit comments