Repository: sympy/sympy
Pull request: Clean up the Union and Intersection constructors
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

Note, this is a backwards incompatible change. In particular, Union and Intersection no longer automatically denest their arguments. You must pass the arguments like `Union(*args)`. There is also a minor change to the way iterating an intersection of symbolic elements works (see the release notes below and the commit messages). 

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests . Please also
write a comment on that issue linking back to this pull request once it is
open. -->

Closes https://github.com/sympy/sympy/pull/15479
Fixes https://github.com/sympy/sympy/issues/15448
Related to https://github.com/sympy/sympy/issues/16342
~~Related to #10249~~

#### Brief description of what is fixed or changed


#### Other comments

There is an XFAIL test here, which is due to https://github.com/sympy/sympy/issues/16342. I don't know how to fix that issue. If someone wants to give a go at fixing up `Intersection._handle_finite_sets`, that would be great. The XFAIL is due to subtle changes in the argument ordering from this change. I'm sure you could get a similar wrong result with different inputs in master.

#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* sets
  * `Union` and `Intersection` now work with Python sets as input.
  * `Union` and `Intersection` no longer automatically denest arguments. This means to pass a list or generator to `Union`/`Intersection`, you must use `Union(*list_of_sets)`. 
  * `Union` and `Intersection` now subclass from `LatticeOp`.
  * Handle non-set objects in `Eq` with `Interval` or `FiniteSet`.
  * fix an infinite recursion issue that could occur taking the intersection of finite sets with symbolic elements.
  * `Intersection` no longer returns an object with an unnecessary `UniversalSet` in some cases (for instance, `Intersection(FiniteSet(0, x), FiniteSet(x, y))` now returns `FiniteSet(x, 0)` instead of `Union({x}, Intersection(FiniteSet(0), UniversalSet()))`).
<!-- END RELEASE NOTES -->


Problem statement:
Union does not work with Python set objects
```pytb
>>> Union({1}, {2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./sympy/sets/sets.py", line 1028, in __new__
    args = flatten(args)
  File "./sympy/sets/sets.py", line 1026, in flatten
    return sum(map(flatten, arg), [])
  File "./sympy/sets/sets.py", line 1026, in flatten
    return sum(map(flatten, arg), [])
  File "./sympy/sets/sets.py", line 1027, in flatten
    raise TypeError("Input must be Sets or iterables of Sets")
TypeError: Input must be Sets or iterables of Sets
```

There are probably other things in the sets module that don't work with Python sets either. Python sets should always be automatically converted to FiniteSet.

This is easy to fix, `Union` should call `_sympify` on its arguments.

Hints from issue discussion:
I will take up this issue. I have just started contributing to sympy, so it might take a while. 
@deepakkavoor are you still working on this one
@FrackeR011 Yes, I am
@FrackeR011  Are you still working on it ?
No i am not . You should ask @deepakkavoor if he is . 
@deepakkavoor  I want to work on this issue .  Are you still working on it ? 
@kesarianubhav alright go ahead
I'd like to work on this issue if @kesarianubhav is not working on it
@m-agboola  , I'm working on it .
I would like to work on this issue if @kesarianubhav  are not working on it.

Commit message: Add a test that Union and Intersection don't accept iterable single arguments

Also that they do accept Python set arguments, and convert them into
FiniteSets.

Patch to review:
diff --git a/sympy/series/sequences.py b/sympy/series/sequences.py
index f6b971786fc4..5c2f930e8cef 100644
--- a/sympy/series/sequences.py
+++ b/sympy/series/sequences.py
@@ -953,7 +953,7 @@ def interval(self):
         """Sequence is defined on the intersection
         of all the intervals of respective sequences
         """
-        return Intersection(a.interval for a in self.args)
+        return Intersection(*(a.interval for a in self.args))
 
     @property
     def start(self):
@@ -1027,7 +1027,7 @@ def _flatten(arg):
         if not args:
             return S.EmptySequence
 
-        if Intersection(a.interval for a in args) is S.EmptySet:
+        if Intersection(*(a.interval for a in args)) is S.EmptySet:
             return S.EmptySequence
 
         # reduce using known rules
@@ -1134,7 +1134,7 @@ def _flatten(arg):
         if not args:
             return S.EmptySequence
 
-        if Intersection(a.interval for a in args) is S.EmptySet:
+        if Intersection(*(a.interval for a in args)) is S.EmptySet:
             return S.EmptySequence
 
         # reduce using known rules
diff --git a/sympy/sets/handlers/functions.py b/sympy/sets/handlers/functions.py
index 6ead4a2b69f2..4c8a9f0937f3 100644
--- a/sympy/sets/handlers/functions.py
+++ b/sympy/sets/handlers/functions.py
@@ -118,14 +118,14 @@ def _set_function(f, x):
 
 @dispatch(FunctionUnion, Union)
 def _set_function(f, x):
-    return Union(imageset(f, arg) for arg in x.args)
+    return Union(*(imageset(f, arg) for arg in x.args))
 
 @dispatch(FunctionUnion, Intersection)
 def _set_function(f, x):
     from sympy.sets.sets import is_function_invertible_in_set
     # If the function is invertible, intersect the maps of the sets.
     if is_function_invertible_in_set(f, x):
-        return Intersection(imageset(f, arg) for arg in x.args)
+        return Intersection(*(imageset(f, arg) for arg in x.args))
     else:
         return ImageSet(Lambda(_x, f(_x)), x)
 
diff --git a/sympy/sets/sets.py b/sympy/sets/sets.py
index 35a0cca8d4e9..b68065a16b62 100644
--- a/sympy/sets/sets.py
+++ b/sympy/sets/sets.py
@@ -5,6 +5,7 @@
 from sympy.core.basic import Basic
 from sympy.core.compatibility import (iterable, with_metaclass,
     ordered, range, PY3)
+from sympy.core.cache import cacheit
 from sympy.core.evalf import EvalfMixin
 from sympy.core.evaluate import global_evaluate
 from sympy.core.expr import Expr
@@ -12,6 +13,7 @@
 from sympy.core.logic import fuzzy_bool
 from sympy.core.mul import Mul
 from sympy.core.numbers import Float
+from sympy.core.operations import LatticeOp
 from sympy.core.relational import Eq, Ne
 from sympy.core.singleton import Singleton, S
 from sympy.core.symbol import Symbol, Dummy, _uniquely_named_symbol
@@ -172,14 +174,14 @@ def _complement(self, other):
                                      zip(self.sets, other.sets))
             product_sets = (ProductSet(*set) for set in switch_sets)
             # Union of all combinations but this one
-            return Union(p for p in product_sets if p != other)
+            return Union(*(p for p in product_sets if p != other))
 
         elif isinstance(other, Interval):
             if isinstance(self, Interval) or isinstance(self, FiniteSet):
                 return Intersection(other, self.complement(S.Reals))
 
         elif isinstance(other, Union):
-            return Union(o - self for o in other.args)
+            return Union(*(o - self for o in other.args))
 
         elif isinstance(other, Complement):
             return Complement(other.args[0], Union(other.args[1], self), evaluate=False)
@@ -656,9 +658,9 @@ def sets(self):
 
     @property
     def _boundary(self):
-        return Union(ProductSet(b + b.boundary if i != j else b.boundary
+        return Union(*(ProductSet(b + b.boundary if i != j else b.boundary
                                 for j, b in enumerate(self.sets))
-                                for i, a in enumerate(self.sets))
+                                for i, a in enumerate(self.sets)))
 
 
     @property
@@ -967,11 +969,10 @@ def as_relational(self, x):
         return And(left, right)
 
     def _eval_Eq(self, other):
-        if not other.is_Interval:
-            if (other.is_Union or other.is_Complement or
-                other.is_Intersection or other.is_ProductSet):
-                return
-
+        if not isinstance(other, Interval):
+            if isinstance(other, Set):
+                if isinstance(other, FiniteSet):
+                    return false
             return false
 
         return And(Eq(self.left, other.left),
@@ -980,7 +981,7 @@ def _eval_Eq(self, other):
                    self.right_open == other.right_open)
 
 
-class Union(Set, EvalfMixin):
+class Union(Set, LatticeOp, EvalfMixin):
     """
     Represents a union of sets as a :class:`Set`.
 
@@ -1009,34 +1010,36 @@ class Union(Set, EvalfMixin):
     """
     is_Union = True
 
-    def __new__(cls, *args, **kwargs):
-        evaluate = kwargs.get('evaluate', global_evaluate[0])
+    @property
+    def identity(self):
+        return S.EmptySet
 
-        # flatten inputs to merge intersections and iterables
-        args = list(args)
+    @property
+    def zero(self):
+        return S.UniversalSet
 
-        def flatten(arg):
-            if isinstance(arg, Set):
-                if arg.is_Union:
-                    return sum(map(flatten, arg.args), [])
-                else:
-                    return [arg]
-            if iterable(arg):  # and not isinstance(arg, Set) (implicit)
-                return sum(map(flatten, arg), [])
-            raise TypeError("Input must be Sets or iterables of Sets")
-        args = flatten(args)
+    def __new__(cls, *args, evaluate=None):
+        if evaluate is None:
+            evaluate = global_evaluate[0]
 
-        # Union of no sets is EmptySet
-        if len(args) == 0:
-            return S.EmptySet
+        # flatten inputs to merge intersections and iterables
+        args = _sympify(args)
 
         # Reduce sets using known rules
         if evaluate:
+            args = list(cls._new_args_filter(args))
             return simplify_union(args)
 
         args = list(ordered(args, Set._infimum_key))
 
-        return Basic.__new__(cls, *args)
+        obj = Basic.__new__(cls, *args)
+        obj._argset = frozenset(args)
+        return obj
+
+    @property
+    @cacheit
+    def args(self):
+        return self._args
 
     def _complement(self, universe):
         # DeMorgan's Law
@@ -1112,7 +1115,7 @@ def boundary_of_set(i):
                 if j != i:
                     b = b - a.interior
             return b
-        return Union(map(boundary_of_set, range(len(self.args))))
+        return Union(*map(boundary_of_set, range(len(self.args))))
 
     def as_relational(self, symbol):
         """Rewrite a Union in terms of equalities and logic operators. """
@@ -1129,7 +1132,7 @@ def is_iterable(self):
 
     def _eval_evalf(self, prec):
         try:
-            return Union(set._eval_evalf(prec) for set in self.args)
+            return Union(*(set._eval_evalf(prec) for set in self.args))
         except (TypeError, ValueError, NotImplementedError):
             import sys
             raise (TypeError("Not all sets are evalf-able"),
@@ -1162,7 +1165,7 @@ def roundrobin(*iterables):
         else:
             raise TypeError("Not all constituent sets are iterable")
 
-class Intersection(Set):
+class Intersection(Set, LatticeOp):
     """
     Represents an intersection of sets as a :class:`Set`.
 
@@ -1190,35 +1193,36 @@ class Intersection(Set):
     """
     is_Intersection = True
 
-    def __new__(cls, *args, **kwargs):
-        evaluate = kwargs.get('evaluate', global_evaluate[0])
-
-        # flatten inputs to merge intersections and iterables
-        args = list(args)
+    @property
+    def identity(self):
+        return S.UniversalSet
 
-        def flatten(arg):
-            if isinstance(arg, Set):
-                if arg.is_Intersection:
-                    return sum(map(flatten, arg.args), [])
-                else:
-                    return [arg]
-            if iterable(arg):  # and not isinstance(arg, Set) (implicit)
-                return sum(map(flatten, arg), [])
-            raise TypeError("Input must be Sets or iterables of Sets")
-        args = flatten(args)
+    @property
+    def zero(self):
+        return S.EmptySet
 
-        if len(args) == 0:
-            return S.UniversalSet
+    def __new__(cls, *args, evaluate=None):
+        if evaluate is None:
+            evaluate = global_evaluate[0]
 
-        # args can't be ordered for Partition see issue #9608
-        if 'Partition' not in [type(a).__name__ for a in args]:
-            args = list(ordered(args, Set._infimum_key))
+        # flatten inputs to merge intersections and iterables
+        args = _sympify(args)
 
         # Reduce sets using known rules
         if evaluate:
+            args = list(cls._new_args_filter(args))
             return simplify_intersection(args)
 
-        return Basic.__new__(cls, *args)
+        args = list(ordered(args, Set._infimum_key))
+
+        obj = Basic.__new__(cls, *args)
+        obj._argset = frozenset(args)
+        return obj
+
+    @property
+    @cacheit
+    def args(self):
+        return self._args
 
     @property
     def is_iterable(self):
@@ -1241,7 +1245,7 @@ def __iter__(self):
             if s.is_iterable:
                 no_iter = False
                 other_sets = set(self.args) - set((s,))
-                other = Intersection(other_sets, evaluate=False)
+                other = Intersection(*other_sets, evaluate=False)
                 for x in s:
                     c = sympify(other.contains(x))
                     if c is S.true:
@@ -1249,7 +1253,7 @@ def __iter__(self):
                     elif c is S.false:
                         pass
                     else:
-                        yield c
+                        raise ValueError("Cannot determine if {x} is contained in the set during iteration".format(x=x))
 
         if no_iter:
             raise ValueError("None of the constituent sets are iterable")
@@ -1263,6 +1267,7 @@ def _handle_finite_sets(args):
             binary=True)
         if not fs_args:
             return
+        fs_args.sort(key=len)
         s = fs_args[0]
         fs_args = fs_args[1:]
 
@@ -1277,6 +1282,7 @@ def _handle_finite_sets(args):
                 unk.append(x)
             else:
                 pass  # drop arg
+
         res = FiniteSet(
             *res, evaluate=False) if res else S.EmptySet
         if unk:
@@ -1311,9 +1317,12 @@ def _handle_finite_sets(args):
             other_sets = Intersection(*other)
             if not other_sets:
                 return S.EmptySet  # b/c we use evaluate=False below
-            res += Intersection(
-                FiniteSet(*unk),
-                other_sets, evaluate=False)
+            elif other_sets == S.UniversalSet:
+                res += FiniteSet(*unk)
+            else:
+                res += Intersection(
+                    FiniteSet(*unk),
+                    other_sets, evaluate=False)
         return res
 
     def as_relational(self, symbol):
@@ -1364,7 +1373,7 @@ def reduce(A, B):
             return EmptySet()
 
         if isinstance(B, Union):
-            return Intersection(s.complement(A) for s in B.args)
+            return Intersection(*(s.complement(A) for s in B.args))
 
         result = B._complement(A)
         if result is not None:
@@ -1531,11 +1540,10 @@ def __new__(cls, *args, **kwargs):
         return obj
 
     def _eval_Eq(self, other):
-        if not other.is_FiniteSet:
-            if (other.is_Union or other.is_Complement or
-                other.is_Intersection or other.is_ProductSet):
-                return
-
+        if not isinstance(other, FiniteSet):
+            if isinstance(other, Set):
+                if isinstance(other, Interval):
+                    return false
             return false
 
         if len(self) != len(other):
@@ -1560,10 +1568,10 @@ def _complement(self, other):
                 intervals.append(Interval(nums[-1], S.Infinity, True, True))
 
                 if syms != []:
-                    return Complement(Union(intervals, evaluate=False),
+                    return Complement(Union(*intervals, evaluate=False),
                             FiniteSet(*syms), evaluate=False)
                 else:
-                    return Union(intervals, evaluate=False)
+                    return Union(*intervals, evaluate=False)
             elif nums == []:
                 return None
 
@@ -1856,6 +1864,13 @@ def simplify_union(args):
     from sympy.sets.handlers.union import union_sets
 
     # ===== Global Rules =====
+    if not args:
+        return S.EmptySet
+
+    for arg in args:
+        if not isinstance(arg, Set):
+            raise TypeError("Input args to Union must be Sets")
+
     # Merge all finite sets
     finite_sets = [x for x in args if x.is_FiniteSet]
     if len(finite_sets) > 1:
@@ -1886,7 +1901,7 @@ def simplify_union(args):
     if len(args) == 1:
         return args.pop()
     else:
-        return Union(args, evaluate=False)
+        return Union(*args, evaluate=False)
 
 
 def simplify_intersection(args):
@@ -1901,6 +1916,13 @@ def simplify_intersection(args):
     """
 
     # ===== Global Rules =====
+    if not args:
+        return S.UniversalSet
+
+    for arg in args:
+        if not isinstance(arg, Set):
+            raise TypeError("Input args to Union must be Sets")
+
     # If any EmptySets return EmptySet
     if any(s.is_EmptySet for s in args):
         return S.EmptySet
@@ -1916,8 +1938,8 @@ def simplify_intersection(args):
         if s.is_Union:
             other_sets = set(args) - set((s,))
             if len(other_sets) > 0:
-                other = Intersection(other_sets)
-                return Union(Intersection(arg, other) for arg in s.args)
+                other = Intersection(*other_sets)
+                return Union(*(Intersection(arg, other) for arg in s.args))
             else:
                 return Union(*[arg for arg in s.args])
 
@@ -1955,7 +1977,7 @@ def simplify_intersection(args):
     if len(args) == 1:
         return args.pop()
     else:
-        return Intersection(args, evaluate=False)
+        return Intersection(*args, evaluate=False)
 
 
 def _handle_finite_sets(op, x, y, commutative):
diff --git a/sympy/sets/tests/test_sets.py b/sympy/sets/tests/test_sets.py
index edb31675b28e..e900684aec83 100644
--- a/sympy/sets/tests/test_sets.py
+++ b/sympy/sets/tests/test_sets.py
@@ -97,7 +97,7 @@ def test_union():
     assert Union(Interval(1, 2), S.EmptySet) == Interval(1, 2)
     assert Union(S.EmptySet) == S.EmptySet
 
-    assert Union(Interval(0, 1), [FiniteSet(1.0/n) for n in range(1, 10)]) == \
+    assert Union(Interval(0, 1), *[FiniteSet(1.0/n) for n in range(1, 10)]) == \
         Interval(0, 1)
 
     assert Interval(1, 2).union(Interval(2, 3)) == \
@@ -143,10 +143,10 @@ def test_union():
 
 def test_union_iter():
     # Use Range because it is ordered
-    u = Union(Range(3), Range(5), Range(3), evaluate=False)
+    u = Union(Range(3), Range(5), Range(4), evaluate=False)
 
     # Round robin
-    assert list(u) == [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 4]
+    assert list(u) == [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4]
 
 def test_difference():
     assert Interval(1, 3) - Interval(1, 2) == Interval(2, 3, True)
@@ -171,8 +171,8 @@ def test_difference():
 def test_Complement():
     assert Complement(Interval(1, 3), Interval(1, 2)) == Interval(2, 3, True)
     assert Complement(FiniteSet(1, 3, 4), FiniteSet(3, 4)) == FiniteSet(1)
-    assert Complement(Union(Interval(0, 2),
-                            FiniteSet(2, 3, 4)), Interval(1, 3)) == \
+    assert Complement(Union(Interval(0, 2), FiniteSet(2, 3, 4)),
+                      Interval(1, 3)) == \
         Union(Interval(0, 1, False, True), FiniteSet(4))
 
     assert not 3 in Complement(Interval(0, 5), Interval(1, 4), evaluate=False)
@@ -312,9 +312,8 @@ def test_intersection():
     assert (2, 2, 2) not in i
     raises(ValueError, lambda: list(i))
 
-    assert Intersection(Intersection(S.Integers, S.Naturals, evaluate=False),
-                        S.Reals, evaluate=False) == \
-            Intersection(S.Integers, S.Naturals, S.Reals, evaluate=False)
+    a = Intersection(Intersection(S.Integers, S.Naturals, evaluate=False), S.Reals, evaluate=False)
+    assert a._argset == frozenset([Intersection(S.Naturals, S.Integers, evaluate=False), S.Reals])
 
     assert Intersection(S.Complexes, FiniteSet(S.ComplexInfinity)) == S.EmptySet
 
@@ -938,7 +937,10 @@ def test_issue_9637():
     assert Complement(a, Interval(1, 3)) == Complement(a, Interval(1, 3), evaluate=False)
 
 
+
+@XFAIL
 def test_issue_9808():
+    # See https://github.com/sympy/sympy/issues/16342
     assert Complement(FiniteSet(y), FiniteSet(1)) == Complement(FiniteSet(y), FiniteSet(1), evaluate=False)
     assert Complement(FiniteSet(1, 2, x), FiniteSet(x, y, 2, 3)) == \
         Complement(FiniteSet(1), FiniteSet(y), evaluate=False)
@@ -981,8 +983,7 @@ def test_issue_10113():
 
 
 def test_issue_10248():
-    assert list(Intersection(S.Reals, FiniteSet(x))) == [
-        And(x < oo, x > -oo)]
+    raises(ValueError, lambda: list(Intersection(S.Reals, FiniteSet(x))))
 
 
 def test_issue_9447():
@@ -1063,3 +1064,34 @@ def test_issue_11174():
 
     soln = Intersection(S.Reals, FiniteSet(x), evaluate=False)
     assert Intersection(FiniteSet(x), S.Reals) == soln
+
+def test_finite_set_intersection():
+    # The following should not produce recursion errors
+    # Note: some of these are not completely correct. See
+    # https://github.com/sympy/sympy/issues/16342.
+    assert Intersection(FiniteSet(-oo, x), FiniteSet(x)) == FiniteSet(x)
+    assert Intersection._handle_finite_sets([FiniteSet(-oo, x), FiniteSet(0, x)]) == FiniteSet(x)
+
+    assert Intersection._handle_finite_sets([FiniteSet(-oo, x), FiniteSet(x)]) == FiniteSet(x)
+    assert Intersection._handle_finite_sets([FiniteSet(2, 3, x, y), FiniteSet(1, 2, x)]) == \
+        Intersection._handle_finite_sets([FiniteSet(1, 2, x), FiniteSet(2, 3, x, y)]) == \
+        Intersection(FiniteSet(1, 2, x), FiniteSet(2, 3, x, y)) == \
+        FiniteSet(1, 2, x)
+
+def test_union_intersection_constructor():
+    # The actual exception does not matter here, so long as these fail
+    sets = [FiniteSet(1), FiniteSet(2)]
+    raises(Exception, lambda: Union(sets))
+    raises(Exception, lambda: Intersection(sets))
+    raises(Exception, lambda: Union(tuple(sets)))
+    raises(Exception, lambda: Intersection(tuple(sets)))
+    raises(Exception, lambda: Union(i for i in sets))
+    raises(Exception, lambda: Intersection(i for i in sets))
+
+    # Python sets are treated the same as FiniteSet
+    # The union of a single set (of sets) is the set (of sets) itself
+    assert Union(set(sets)) == FiniteSet(*sets)
+    assert Intersection(set(sets)) == FiniteSet(*sets)
+
+    assert Union({1}, {2}) == FiniteSet(1, 2)
+    assert Intersection({1, 2}, {2, 3}) == FiniteSet(2)
diff --git a/sympy/solvers/inequalities.py b/sympy/solvers/inequalities.py
index 84325fd4c6a3..64558b8293aa 100644
--- a/sympy/solvers/inequalities.py
+++ b/sympy/solvers/inequalities.py
@@ -126,7 +126,7 @@ def solve_poly_inequalities(polys):
     Union(Interval.open(-oo, -sqrt(3)), Interval.open(-1, 1), Interval.open(sqrt(3), oo))
     """
     from sympy import Union
-    return Union(*[solve_poly_inequality(*p) for p in polys])
+    return Union(*[Union(*solve_poly_inequality(*p)) for p in polys])
 
 
 def solve_rational_inequalities(eqs):
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 49469d2aa78b..ad2df78a5f70 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -129,7 +129,7 @@ def _invert(f_x, y, x, domain=S.Complexes):
     >>> invert_complex(exp(x), y, x)
     (x, ImageSet(Lambda(_n, I*(2*_n*pi + arg(y)) + log(Abs(y))), Integers))
     >>> invert_real(exp(x), y, x)
-    (x, Intersection(Reals, {log(y)}))
+    (x, Intersection({log(y)}, Reals))
 
     When does exp(x) == 1?
 


Code review comments:
diff_hunk: @@ -1009,34 +1010,36 @@ class Union(Set, EvalfMixin):
     """
     is_Union = True
 
-    def __new__(cls, *args, **kwargs):
-        evaluate = kwargs.get('evaluate', global_evaluate[0])
+    @property
+    def identity(self):
+        return S.EmptySet
 
-        # flatten inputs to merge intersections and iterables
-        args = list(args)
+    @property
+    def zero(self):
+        return S.UniversalSet
 
-        def flatten(arg):
-            if isinstance(arg, Set):
-                if arg.is_Union:
-                    return sum(map(flatten, arg.args), [])
-                else:
-                    return [arg]
-            if iterable(arg):  # and not isinstance(arg, Set) (implicit)
-                return sum(map(flatten, arg), [])
-            raise TypeError("Input must be Sets or iterables of Sets")
-        args = flatten(args)
+    def __new__(cls, *args, evaluate=None):
path: sympy/sets/sets.py
text: @user1:
This doesn't work on 2.7.

@author:
Ah crap. I always forget about this.