def of_string(s: str) -> Lens:
        """
        Convert a string representing a Python expression into a Lens.
        """

        # NOTE(piotrm): we use python parser for this which means only things
        # which are valid python expressions (with additional constraints) can
        # be converted.

        if len(s) == 0:
            return Lens()

        try:
            # NOTE: "eval" here means to parse an expression, not a statement.
            # exp = parse(f"PLACEHOLDER.{s}", mode="eval")
            exp = parse(s, mode="eval")

        except SyntaxError:
            raise ParseException(s, None)

        if not isinstance(exp, ast.Expression):
            raise ParseException(s, exp)

        exp = exp.body

        path = []

        def of_index(idx):
            if isinstance(idx, ast.Tuple):
                elts = tuple(of_index(elt) for elt in idx.elts)

                if all(isinstance(e, GetItem) for e in elts):
                    return GetItems(items=tuple(e.item for e in elts))

                elif all(isinstance(e, GetIndex) for e in elts):
                    return GetIndices(indices=tuple(e.index for e in elts))

                else:
                    raise ParseException(s, idx)

            elif isinstance(idx, ast.Constant):
                if isinstance(idx.value, str):
                    return GetItem(item=idx.value)

                elif isinstance(idx.value, int):
                    return GetIndex(index=idx.value)

                else:
                    raise ParseException(s, idx)

            elif isinstance(idx, ast.UnaryOp):
                if isinstance(idx.op, ast.USub):
                    oper = of_index(idx.operand)
                    if not isinstance(oper, GetIndex):
                        raise ParseException(s, idx)

                    return GetIndex(index=-oper.index)

            elif idx is None:
                return None

            else:
                raise ParseException(s, exp)

        while exp is not None:
            if isinstance(exp, ast.Attribute):
                attr_name = exp.attr
                path.append(GetItemOrAttribute(item_or_attribute=attr_name))
                exp = exp.value

            elif isinstance(exp, ast.Subscript):
                sub = exp.slice

                if isinstance(sub, ast.Index):
                    step = of_index(sub.value)

                    path.append(step)

                elif isinstance(sub, ast.Slice):
                    vals: Tuple[GetIndex, ...] = tuple(
                        of_index(v) for v in (sub.lower, sub.upper, sub.step)
                    )

                    if not all(
                        e is None or isinstance(e, GetIndex) for e in vals
                    ):
                        raise ParseException(s, exp)

                    vals_indices: Tuple[Union[None, int], ...] = tuple(
                        None if e is None else e.index for e in vals
                    )

                    path.append(
                        GetSlice(
                            start=vals_indices[0],
                            stop=vals_indices[1],
                            step=vals_indices[2],
                        )
                    )

                elif isinstance(sub, ast.Tuple):
                    path.append(of_index(sub))

                elif isinstance(sub, ast.Constant):
                    path.append(of_index(sub))

                else:
                    raise ParseException(s, exp)

                exp = exp.value

            elif isinstance(exp, ast.List):
                # Need this case for paths that do not start with item/attribute
                # but instead go directly to something bracketed, which is a
                # list in python syntax.

                if not all(isinstance(el, ast.Constant) for el in exp.elts):
                    raise ParseException(s, exp)

                if len(exp.elts) == 0:
                    logger.warning(
                        f"Path {s} is getting zero items/indices, it will not produce anything."
                    )
                    path.append(GetIndices(indices=()))

                elif len(exp.elts) == 1:
                    el = exp.elts[0]
                    if isinstance(el.value, int):
                        path.append(GetIndex(index=el.value))
                    elif isinstance(el.value, str):
                        path.append(GetItem(item=el.value))
                    else:
                        raise ParseException(s, exp)

                else:
                    if all(isinstance(el.value, int) for el in exp.elts):
                        path.append(
                            GetIndices(
                                indices=tuple(el.value for el in exp.elts)
                            )
                        )

                    elif all(isinstance(el.value, str) for el in exp.elts):
                        path.append(
                            GetItems(items=tuple(el.value for el in exp.elts))
                        )

                    else:
                        raise ParseException(s, exp)

                exp = None

            elif isinstance(exp, ast.Name):
                path.append(GetItemOrAttribute(item_or_attribute=exp.id))

                exp = None

            elif isinstance(exp, ast.Call):
                if not isinstance(exp.func, ast.Attribute):
                    raise ParseException(s, exp)

                funcname = exp.func.attr
                if funcname == "collect":
                    path.append(Collect())

                else:
                    raise TypeError(
                        f"`{funcname}` is not a handled call for paths."
                    )

                if len(exp.args) + len(exp.keywords) != 0:
                    logger.warning(f"args/kwargs for `{funcname}` are ignored")

                exp = exp.func.value

            else:
                raise ParseException(s, exp)

        return Lens(path=path[::-1])

        Convert a string representing a Python expression into a Lens.
        
src/core/trulens/core/utils/serial.py