docs: def join(
              self,
              inner_enumerable,
              outer_key=lambda x: x,
              inner_key=lambda x: x,
              result_func=lambda x: x
      ):
          """
          Return enumerable of inner equi-join between two enumerables
          :param inner_enumerable: inner enumerable to join to self
          :param outer_key: key selector of outer enumerable as lambda expression
          :param inner_key: key selector of inner enumerable as lambda expression
          :param result_func: lambda expression to transform result of join
          :return: new Enumerable object
          """
          if not isinstance(inner_enumerable, Enumerable3):
              raise TypeError(
                  u"inner_enumerable parameter must be an instance of Enumerable"
              )
          return Enumerable3(
              itertools.product(
                  filter(
                      lambda x: outer_key(x) in map(inner_key, inner_enumerable),
                      self
                  ),
                  filter(
                      lambda y: inner_key(y) in map(outer_key, self),
                      inner_enumerable
                  )
              )
          )\
              .where(lambda x: outer_key(x[0]) == inner_key(x[1]))\
              .select(result_func)