2

Recently, I needed to find the intersection of a collection of sets, i.e. S1 ∩ S2 ∩ ... ∩ Sn in Python. I found a very interesting answer at Best way to find the intersection of multiple sets? so that my code solution was:

s1, s2, s3 = set("abc"), set("cde"), set("acf")
list_of_sets = [s1, s2, s3]
result = set.intersection(*list_of_sets)   # {'c'}

I was surprised to see .intersection() called on set the class rather than on a set object.

My question is where can I see documentation for this?

The above linked StackOverflow answer says

Note that set.intersection is not a static method, but this uses the functional notation to apply intersection of the first set with the rest of the list. So if the argument list is empty this will fail.

but the official docs do not seem to describe this: intersection(*others). Searching "python set functional notation" doesn't seem to help either. The official docs describe what I was expecting, which was to use .intersection() on a set object, e.g.

result = s1.intersection(*[s2, s3])

I'm also interested to know what other methods from other data structures can be used in this same way.

James Pringle
  • 1,079
  • 6
  • 15
  • It's called "iterable packing" and is described in the [Expression list](https://docs.python.org/3/reference/expressions.html#expression-lists) section (and other places) in the documentation. It's a general feature not specific to `set`s. – martineau Dec 07 '20 at 15:40
  • @martineau that wasn't the intent of my question. I'm not asking about iterable packing. I'm asking about calling the method `intersection` on `set` (the class) rather than an object. I'll make some edits so that is clear. – James Pringle Dec 07 '20 at 16:12

2 Answers2

2

I think it's just an (undocumented) artifact from the way set.itersection(*others) is implemented and therefore should apply to any other classes with methods that support a variable number of other objects.

To illustrate and prove my point, here's a custom class that supports this so-called "functional notation" via its own intersection() method.

class Class:
    def __init__(self, value=''):
        self.value = str(value)
        self.type = type(value)
        pass

    def __repr__(self):
        return f'Class({self.type(self.value)!r})'

    def intersection(self, *others):
        result = self
        for other in others:
            result &= other  # Perform via __and__() method.
        return result

    def __and__(self, other):
        return type(self)('|'.join((self.value, other.value)))


c1 = Class('foo')
c2 = Class('bar')
c3 = Class(42)
print(f'c1: {c1}')
print(f'c2: {c2}')
print(f'c3: {c3}')

# Both produce the same result.
print(c1 & c2 & c3)
print(Class.intersection(c1, c2, c3))

Output

c1: Class('foo')
c2: Class('bar')
c3: Class(42)
Class('foo|bar|42')
Class('foo|bar|42')
martineau
  • 119,623
  • 25
  • 170
  • 301
1

In addition to martineau answer, a method called through a class instance like obj1.method1(...) can also be called with obj1_class.method1(obj1, ...), and in the C implementation code I only see the assumption of the first argument to be a set. That is what happens too when you unpack others, this being a list of sets.

Maybe it is undocumented in the method help, but should be expected anyway.

So I also agree with

and therefore should apply to any other classes with methods that support a variable number of other objects.

progmatico
  • 4,714
  • 1
  • 16
  • 27
  • This is exactly it. `obj.method(...)` is equivalent to `ObjClass.method(obj, ...)`. Thanks for the reminder. From other programming languages, I'm used to having to instantiate an object before being able to call instance methods. But Python is a little different. – James Pringle Dec 07 '20 at 20:57
  • 1
    `Class.method` is called an unbound method which has to do with the descriptor protocol. See [What is the difference between a function, an unbound method and a bound method?](https://stackoverflow.com/questions/11949808/what-is-the-difference-between-a-function-an-unbound-method-and-a-bound-method). – martineau Dec 07 '20 at 21:17