There are two issues here...
First, it would seem like you are not testing membership at the correct depth of a nested data structure. When you call set(combinations(pairs, i)), you get a structure that is 3 levels deep: A set of tuples of tuples of ints (ints 3 containers deep).
>>> pairs = [(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)]
>>> test = set(combinations(pairs,1))
>>> test
{((3, 2),), ((2, 3),), ((1, 1),), ((2, 2),), ((3, 1),), ((1, 3),), ((1, 2),), ((3, 3),), ((2, 1),)}
It is perfectly valid to test if a specific tuple of tuples of ints is contained within the set, but those tuples aren't automatically flattened for you to be able to test against a simple tuple of ints.
>>> ((1,1),) in test
True
>>> (1,1) in test
False
If you want to check if any tuples within the set contain a specific sub-tuple, you'll have to iterate over the set and check each top level tuple individually (hint: things like map can make this iteration a little shorter and sweeter)
for top_tuple in test:
if (1,1) in top_tuple:
print("found it!")
Second, is a somewhat common trap for new python programmers, which is chaining logical operators. You must think of and or in etc.. as similar to mathematical operators similar to + - * / etc.. The other important thing is how the logical operators treat things that aren't True and False. In general python treats things that are empty such as empty lists, strings, tuples, sets, etc.. as False, as well as things that are equal to 0. Basically everything else non-zero or non-empty is treated as True. Then when you run into an and, if the first value (on the left) is True-ish the return value of the and statement will be whatever is on the right. if The first value is False-ish, the return value will be that first value. When you chain them together, they get evaluated left to right.
>>> (1,1) and "cookies"
"cookies"
>>> False and "cookies"
False
>>> (2,3) and (2,2) and (3,2)
(3, 2)