We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
BUG: Multiple aliases on the same column not behaving as expected

``` python
column = table.some_column
table.projection(
        [
            column.name("alias1"),
            column.name("alias2"),
            column.name("alias3"),
        ]
    )
```

I think the expected behavior would be a table expression with:
```
Selection[table]
  table:
    Table: ref_0
  selections:
    alias1 = Column[float64*] 'some_column' from table
      ref_0
    alias2 = Column[float64*] 'some_column' from table
      ref_0
    alias3 = Column[float64*] 'some_column' from table
      ref_0
```

However, the result I'm getting is:
```
Selection[table]
  table:
    Table: ref_0
  selections:
    alias1 = Column[float64*] 'some_column' from table
      ref_0
    alias1 = Column[float64*] 'some_column' from table
      ref_0
    alias1 = Column[float64*] 'some_column' from table
      ref_0
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ibis/expr/format.py`
Content:
```
1 import ibis.expr.operations as ops
2 import ibis.expr.types as ir
3 import ibis.util as util
4 
5 
6 class FormatMemo:
7     # A little sanity hack to simplify the below
8 
9     def __init__(self):
10         from collections import defaultdict
11 
12         self.formatted = {}
13         self.aliases = {}
14         self.ops = {}
15         self.counts = defaultdict(int)
16         self._repr_memo = {}
17         self.subexprs = {}
18         self.visit_memo = set()
19 
20     def __contains__(self, obj):
21         return self._key(obj) in self.formatted
22 
23     def _key(self, expr):
24         memo = self._repr_memo
25         try:
26             result = memo[expr]
27         except KeyError:
28             result = memo[expr] = self._format(expr)
29         return result
30 
31     def _format(self, expr):
32         return expr.op()._repr(memo=self)
33 
34     def observe(self, expr, formatter=None):
35         if formatter is None:
36             formatter = self._format
37         key = self._key(expr)
38         if key not in self.formatted:
39             self.aliases[key] = 'ref_{:d}'.format(len(self.formatted))
40             self.formatted[key] = formatter(expr)
41             self.ops[key] = expr.op()
42 
43         self.counts[key] += 1
44 
45     def count(self, expr):
46         return self.counts[self._key(expr)]
47 
48     def get_alias(self, expr):
49         return self.aliases[self._key(expr)]
50 
51     def get_formatted(self, expr):
52         return self.formatted[self._key(expr)]
53 
54 
55 class ExprFormatter:
56     """For creating a nice tree-like representation of an expression graph.
57 
58     Notes
59     -----
60     TODO: detect reused DAG nodes and do not display redundant information
61 
62     """
63 
64     def __init__(
65         self, expr, indent_size=2, base_level=0, memo=None, memoize=True
66     ):
67         self.expr = expr
68         self.indent_size = indent_size
69         self.base_level = base_level
70 
71         self.memoize = memoize
72 
73         # For tracking "extracted" objects, like tables, that we don't want to
74         # print out more than once, and simply alias in the expression tree
75         if memo is None:
76             memo = FormatMemo()
77 
78         self.memo = memo
79 
80     def get_result(self):
81         what = self.expr.op()
82 
83         if self.memoize:
84             self._memoize_tables()
85 
86         if isinstance(what, ops.TableNode) and what.has_schema():
87             # This should also catch aggregations
88             if not self.memoize and self.expr in self.memo:
89                 text = 'Table: %s' % self.memo.get_alias(self.expr)
90             elif isinstance(what, ops.PhysicalTable):
91                 text = self._format_table(self.expr)
92             else:
93                 # Any other node type
94                 text = self._format_node(self.expr)
95         elif isinstance(what, ops.TableColumn):
96             text = self._format_column(self.expr)
97         elif isinstance(what, ops.Literal):
98             text = 'Literal[{}]\n  {}'.format(
99                 self._get_type_display(), str(what.value)
100             )
101         elif isinstance(what, ops.ScalarParameter):
102             text = 'ScalarParameter[{}]'.format(self._get_type_display())
103         elif isinstance(what, ops.Node):
104             text = self._format_node(self.expr)
105 
106         if isinstance(self.expr, ir.ValueExpr) and self.expr._name is not None:
107             text = '{} = {}'.format(self.expr.get_name(), text)
108 
109         if self.memoize:
110             alias_to_text = [
111                 (
112                     self.memo.aliases[x],
113                     self.memo.formatted[x],
114                     self.memo.ops[x],
115                 )
116                 for x in self.memo.formatted
117             ]
118             alias_to_text.sort()
119 
120             # A hack to suppress printing out of a ref that is the result of
121             # the top level expression
122             refs = [
123                 x + '\n' + y
124                 for x, y, op in alias_to_text
125                 if not op.equals(what)
126             ]
127 
128             text = '\n\n'.join(refs + [text])
129 
130         return self._indent(text, self.base_level)
131 
132     def _memoize_tables(self):
133         table_memo_ops = (ops.Aggregation, ops.Selection, ops.SelfReference)
134         expr = self.expr
135         if expr.op() in self.memo.visit_memo:
136             return
137 
138         stack = [expr]
139         seen = set()
140         memo = self.memo
141 
142         while stack:
143             e = stack.pop()
144             op = e.op()
145 
146             if op not in seen:
147                 seen.add(op)
148 
149                 if isinstance(op, ops.PhysicalTable):
150                     memo.observe(e, self._format_table)
151                 elif isinstance(op, ops.Node):
152                     stack.extend(
153                         arg
154                         for arg in reversed(op.args)
155                         if isinstance(arg, ir.Expr)
156                     )
157                     if isinstance(op, table_memo_ops):
158                         memo.observe(e, self._format_node)
159                 elif isinstance(op, ops.TableNode) and op.has_schema():
160                     memo.observe(e, self._format_table)
161                 memo.visit_memo.add(op)
162 
163     def _indent(self, text, indents=1):
164         return util.indent(text, self.indent_size * indents)
165 
166     def _format_table(self, expr):
167         table = expr.op()
168         # format the schema
169         rows = ['name: {}\nschema:'.format(table.name)]
170         rows.extend(
171             map('  {} : {}'.format, table.schema.names, table.schema.types)
172         )
173         opname = type(table).__name__
174         type_display = self._get_type_display(expr)
175         opline = '{}[{}]'.format(opname, type_display)
176         return '{}\n{}'.format(opline, self._indent('\n'.join(rows)))
177 
178     def _format_column(self, expr):
179         # HACK: if column is pulled from a Filter of another table, this parent
180         # will not be found in the memo
181         col = expr.op()
182         parent = col.parent()
183 
184         if parent not in self.memo:
185             self.memo.observe(parent, formatter=self._format_node)
186 
187         table_formatted = self.memo.get_alias(parent)
188         table_formatted = self._indent(table_formatted)
189 
190         type_display = self._get_type_display(self.expr)
191         return "Column[{0}] '{1}' from table\n{2}".format(
192             type_display, col.name, table_formatted
193         )
194 
195     def _format_node(self, expr):
196         op = expr.op()
197         formatted_args = []
198 
199         def visit(what, extra_indents=0):
200             if isinstance(what, ir.Expr):
201                 result = self._format_subexpr(what)
202             else:
203                 result = self._indent(str(what))
204 
205             if extra_indents > 0:
206                 result = util.indent(result, self.indent_size)
207 
208             formatted_args.append(result)
209 
210         arg_names = getattr(op, 'display_argnames', op.argnames)
211 
212         if not arg_names:
213             for arg in op.flat_args():
214                 visit(arg)
215         else:
216             signature = op.signature
217             arg_name_pairs = (
218                 (arg, name)
219                 for arg, name in zip(op.args, arg_names)
220                 if signature[name].show
221             )
222             for arg, name in arg_name_pairs:
223                 if name == 'arg' and isinstance(op, ops.ValueOp):
224                     # don't display first argument's name in repr
225                     name = None
226                 if name is not None:
227                     name = self._indent('{}:'.format(name))
228                 if util.is_iterable(arg):
229                     if name is not None and len(arg) > 0:
230                         formatted_args.append(name)
231                         indents = 1
232                     else:
233                         indents = 0
234                     for x in arg:
235                         visit(x, extra_indents=indents)
236                 else:
237                     if name is not None:
238                         formatted_args.append(name)
239                         indents = 1
240                     else:
241                         indents = 0
242                     visit(arg, extra_indents=indents)
243 
244         opname = type(op).__name__
245         type_display = self._get_type_display(expr)
246         opline = '{}[{}]'.format(opname, type_display)
247         return '\n'.join([opline] + formatted_args)
248 
249     def _format_subexpr(self, expr):
250         subexprs = self.memo.subexprs
251         key = expr.op()
252         try:
253             result = subexprs[key]
254         except KeyError:
255             formatter = ExprFormatter(expr, memo=self.memo, memoize=False)
256             result = subexprs[key] = self._indent(formatter.get_result(), 1)
257         return result
258 
259     def _get_type_display(self, expr=None):
260         if expr is None:
261             expr = self.expr
262         return expr._type_display()
263
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ibis/expr/format.py b/ibis/expr/format.py
--- a/ibis/expr/format.py
+++ b/ibis/expr/format.py
@@ -248,7 +248,7 @@
 
     def _format_subexpr(self, expr):
         subexprs = self.memo.subexprs
-        key = expr.op()
+        key = expr._key
         try:
             result = subexprs[key]
         except KeyError:

{"golden_diff": "diff --git a/ibis/expr/format.py b/ibis/expr/format.py\n--- a/ibis/expr/format.py\n+++ b/ibis/expr/format.py\n@@ -248,7 +248,7 @@\n \n     def _format_subexpr(self, expr):\n         subexprs = self.memo.subexprs\n-        key = expr.op()\n+        key = expr._key\n         try:\n             result = subexprs[key]\n         except KeyError:\n", "issue": "BUG: Multiple aliases on the same column not behaving as expected\n\r\n``` python\r\ncolumn = table.some_column\r\ntable.projection(\r\n        [\r\n            column.name(\"alias1\"),\r\n            column.name(\"alias2\"),\r\n            column.name(\"alias3\"),\r\n        ]\r\n    )\r\n```\r\n\r\nI think the expected behavior would be a table expression with:\r\n```\r\nSelection[table]\r\n  table:\r\n    Table: ref_0\r\n  selections:\r\n    alias1 = Column[float64*] 'some_column' from table\r\n      ref_0\r\n    alias2 = Column[float64*] 'some_column' from table\r\n      ref_0\r\n    alias3 = Column[float64*] 'some_column' from table\r\n      ref_0\r\n```\r\n\r\nHowever, the result I'm getting is:\r\n```\r\nSelection[table]\r\n  table:\r\n    Table: ref_0\r\n  selections:\r\n    alias1 = Column[float64*] 'some_column' from table\r\n      ref_0\r\n    alias1 = Column[float64*] 'some_column' from table\r\n      ref_0\r\n    alias1 = Column[float64*] 'some_column' from table\r\n      ref_0\r\n```\n", "before_files": [{"content": "import ibis.expr.operations as ops\nimport ibis.expr.types as ir\nimport ibis.util as util\n\n\nclass FormatMemo:\n    # A little sanity hack to simplify the below\n\n    def __init__(self):\n        from collections import defaultdict\n\n        self.formatted = {}\n        self.aliases = {}\n        self.ops = {}\n        self.counts = defaultdict(int)\n        self._repr_memo = {}\n        self.subexprs = {}\n        self.visit_memo = set()\n\n    def __contains__(self, obj):\n        return self._key(obj) in self.formatted\n\n    def _key(self, expr):\n        memo = self._repr_memo\n        try:\n            result = memo[expr]\n        except KeyError:\n            result = memo[expr] = self._format(expr)\n        return result\n\n    def _format(self, expr):\n        return expr.op()._repr(memo=self)\n\n    def observe(self, expr, formatter=None):\n        if formatter is None:\n            formatter = self._format\n        key = self._key(expr)\n        if key not in self.formatted:\n            self.aliases[key] = 'ref_{:d}'.format(len(self.formatted))\n            self.formatted[key] = formatter(expr)\n            self.ops[key] = expr.op()\n\n        self.counts[key] += 1\n\n    def count(self, expr):\n        return self.counts[self._key(expr)]\n\n    def get_alias(self, expr):\n        return self.aliases[self._key(expr)]\n\n    def get_formatted(self, expr):\n        return self.formatted[self._key(expr)]\n\n\nclass ExprFormatter:\n    \"\"\"For creating a nice tree-like representation of an expression graph.\n\n    Notes\n    -----\n    TODO: detect reused DAG nodes and do not display redundant information\n\n    \"\"\"\n\n    def __init__(\n        self, expr, indent_size=2, base_level=0, memo=None, memoize=True\n    ):\n        self.expr = expr\n        self.indent_size = indent_size\n        self.base_level = base_level\n\n        self.memoize = memoize\n\n        # For tracking \"extracted\" objects, like tables, that we don't want to\n        # print out more than once, and simply alias in the expression tree\n        if memo is None:\n            memo = FormatMemo()\n\n        self.memo = memo\n\n    def get_result(self):\n        what = self.expr.op()\n\n        if self.memoize:\n            self._memoize_tables()\n\n        if isinstance(what, ops.TableNode) and what.has_schema():\n            # This should also catch aggregations\n            if not self.memoize and self.expr in self.memo:\n                text = 'Table: %s' % self.memo.get_alias(self.expr)\n            elif isinstance(what, ops.PhysicalTable):\n                text = self._format_table(self.expr)\n            else:\n                # Any other node type\n                text = self._format_node(self.expr)\n        elif isinstance(what, ops.TableColumn):\n            text = self._format_column(self.expr)\n        elif isinstance(what, ops.Literal):\n            text = 'Literal[{}]\\n  {}'.format(\n                self._get_type_display(), str(what.value)\n            )\n        elif isinstance(what, ops.ScalarParameter):\n            text = 'ScalarParameter[{}]'.format(self._get_type_display())\n        elif isinstance(what, ops.Node):\n            text = self._format_node(self.expr)\n\n        if isinstance(self.expr, ir.ValueExpr) and self.expr._name is not None:\n            text = '{} = {}'.format(self.expr.get_name(), text)\n\n        if self.memoize:\n            alias_to_text = [\n                (\n                    self.memo.aliases[x],\n                    self.memo.formatted[x],\n                    self.memo.ops[x],\n                )\n                for x in self.memo.formatted\n            ]\n            alias_to_text.sort()\n\n            # A hack to suppress printing out of a ref that is the result of\n            # the top level expression\n            refs = [\n                x + '\\n' + y\n                for x, y, op in alias_to_text\n                if not op.equals(what)\n            ]\n\n            text = '\\n\\n'.join(refs + [text])\n\n        return self._indent(text, self.base_level)\n\n    def _memoize_tables(self):\n        table_memo_ops = (ops.Aggregation, ops.Selection, ops.SelfReference)\n        expr = self.expr\n        if expr.op() in self.memo.visit_memo:\n            return\n\n        stack = [expr]\n        seen = set()\n        memo = self.memo\n\n        while stack:\n            e = stack.pop()\n            op = e.op()\n\n            if op not in seen:\n                seen.add(op)\n\n                if isinstance(op, ops.PhysicalTable):\n                    memo.observe(e, self._format_table)\n                elif isinstance(op, ops.Node):\n                    stack.extend(\n                        arg\n                        for arg in reversed(op.args)\n                        if isinstance(arg, ir.Expr)\n                    )\n                    if isinstance(op, table_memo_ops):\n                        memo.observe(e, self._format_node)\n                elif isinstance(op, ops.TableNode) and op.has_schema():\n                    memo.observe(e, self._format_table)\n                memo.visit_memo.add(op)\n\n    def _indent(self, text, indents=1):\n        return util.indent(text, self.indent_size * indents)\n\n    def _format_table(self, expr):\n        table = expr.op()\n        # format the schema\n        rows = ['name: {}\\nschema:'.format(table.name)]\n        rows.extend(\n            map('  {} : {}'.format, table.schema.names, table.schema.types)\n        )\n        opname = type(table).__name__\n        type_display = self._get_type_display(expr)\n        opline = '{}[{}]'.format(opname, type_display)\n        return '{}\\n{}'.format(opline, self._indent('\\n'.join(rows)))\n\n    def _format_column(self, expr):\n        # HACK: if column is pulled from a Filter of another table, this parent\n        # will not be found in the memo\n        col = expr.op()\n        parent = col.parent()\n\n        if parent not in self.memo:\n            self.memo.observe(parent, formatter=self._format_node)\n\n        table_formatted = self.memo.get_alias(parent)\n        table_formatted = self._indent(table_formatted)\n\n        type_display = self._get_type_display(self.expr)\n        return \"Column[{0}] '{1}' from table\\n{2}\".format(\n            type_display, col.name, table_formatted\n        )\n\n    def _format_node(self, expr):\n        op = expr.op()\n        formatted_args = []\n\n        def visit(what, extra_indents=0):\n            if isinstance(what, ir.Expr):\n                result = self._format_subexpr(what)\n            else:\n                result = self._indent(str(what))\n\n            if extra_indents > 0:\n                result = util.indent(result, self.indent_size)\n\n            formatted_args.append(result)\n\n        arg_names = getattr(op, 'display_argnames', op.argnames)\n\n        if not arg_names:\n            for arg in op.flat_args():\n                visit(arg)\n        else:\n            signature = op.signature\n            arg_name_pairs = (\n                (arg, name)\n                for arg, name in zip(op.args, arg_names)\n                if signature[name].show\n            )\n            for arg, name in arg_name_pairs:\n                if name == 'arg' and isinstance(op, ops.ValueOp):\n                    # don't display first argument's name in repr\n                    name = None\n                if name is not None:\n                    name = self._indent('{}:'.format(name))\n                if util.is_iterable(arg):\n                    if name is not None and len(arg) > 0:\n                        formatted_args.append(name)\n                        indents = 1\n                    else:\n                        indents = 0\n                    for x in arg:\n                        visit(x, extra_indents=indents)\n                else:\n                    if name is not None:\n                        formatted_args.append(name)\n                        indents = 1\n                    else:\n                        indents = 0\n                    visit(arg, extra_indents=indents)\n\n        opname = type(op).__name__\n        type_display = self._get_type_display(expr)\n        opline = '{}[{}]'.format(opname, type_display)\n        return '\\n'.join([opline] + formatted_args)\n\n    def _format_subexpr(self, expr):\n        subexprs = self.memo.subexprs\n        key = expr.op()\n        try:\n            result = subexprs[key]\n        except KeyError:\n            formatter = ExprFormatter(expr, memo=self.memo, memoize=False)\n            result = subexprs[key] = self._indent(formatter.get_result(), 1)\n        return result\n\n    def _get_type_display(self, expr=None):\n        if expr is None:\n            expr = self.expr\n        return expr._type_display()\n", "path": "ibis/expr/format.py"}], "after_files": [{"content": "import ibis.expr.operations as ops\nimport ibis.expr.types as ir\nimport ibis.util as util\n\n\nclass FormatMemo:\n    # A little sanity hack to simplify the below\n\n    def __init__(self):\n        from collections import defaultdict\n\n        self.formatted = {}\n        self.aliases = {}\n        self.ops = {}\n        self.counts = defaultdict(int)\n        self._repr_memo = {}\n        self.subexprs = {}\n        self.visit_memo = set()\n\n    def __contains__(self, obj):\n        return self._key(obj) in self.formatted\n\n    def _key(self, expr):\n        memo = self._repr_memo\n        try:\n            result = memo[expr]\n        except KeyError:\n            result = memo[expr] = self._format(expr)\n        return result\n\n    def _format(self, expr):\n        return expr.op()._repr(memo=self)\n\n    def observe(self, expr, formatter=None):\n        if formatter is None:\n            formatter = self._format\n        key = self._key(expr)\n        if key not in self.formatted:\n            self.aliases[key] = 'ref_{:d}'.format(len(self.formatted))\n            self.formatted[key] = formatter(expr)\n            self.ops[key] = expr.op()\n\n        self.counts[key] += 1\n\n    def count(self, expr):\n        return self.counts[self._key(expr)]\n\n    def get_alias(self, expr):\n        return self.aliases[self._key(expr)]\n\n    def get_formatted(self, expr):\n        return self.formatted[self._key(expr)]\n\n\nclass ExprFormatter:\n    \"\"\"For creating a nice tree-like representation of an expression graph.\n\n    Notes\n    -----\n    TODO: detect reused DAG nodes and do not display redundant information\n\n    \"\"\"\n\n    def __init__(\n        self, expr, indent_size=2, base_level=0, memo=None, memoize=True\n    ):\n        self.expr = expr\n        self.indent_size = indent_size\n        self.base_level = base_level\n\n        self.memoize = memoize\n\n        # For tracking \"extracted\" objects, like tables, that we don't want to\n        # print out more than once, and simply alias in the expression tree\n        if memo is None:\n            memo = FormatMemo()\n\n        self.memo = memo\n\n    def get_result(self):\n        what = self.expr.op()\n\n        if self.memoize:\n            self._memoize_tables()\n\n        if isinstance(what, ops.TableNode) and what.has_schema():\n            # This should also catch aggregations\n            if not self.memoize and self.expr in self.memo:\n                text = 'Table: %s' % self.memo.get_alias(self.expr)\n            elif isinstance(what, ops.PhysicalTable):\n                text = self._format_table(self.expr)\n            else:\n                # Any other node type\n                text = self._format_node(self.expr)\n        elif isinstance(what, ops.TableColumn):\n            text = self._format_column(self.expr)\n        elif isinstance(what, ops.Literal):\n            text = 'Literal[{}]\\n  {}'.format(\n                self._get_type_display(), str(what.value)\n            )\n        elif isinstance(what, ops.ScalarParameter):\n            text = 'ScalarParameter[{}]'.format(self._get_type_display())\n        elif isinstance(what, ops.Node):\n            text = self._format_node(self.expr)\n\n        if isinstance(self.expr, ir.ValueExpr) and self.expr._name is not None:\n            text = '{} = {}'.format(self.expr.get_name(), text)\n\n        if self.memoize:\n            alias_to_text = [\n                (\n                    self.memo.aliases[x],\n                    self.memo.formatted[x],\n                    self.memo.ops[x],\n                )\n                for x in self.memo.formatted\n            ]\n            alias_to_text.sort()\n\n            # A hack to suppress printing out of a ref that is the result of\n            # the top level expression\n            refs = [\n                x + '\\n' + y\n                for x, y, op in alias_to_text\n                if not op.equals(what)\n            ]\n\n            text = '\\n\\n'.join(refs + [text])\n\n        return self._indent(text, self.base_level)\n\n    def _memoize_tables(self):\n        table_memo_ops = (ops.Aggregation, ops.Selection, ops.SelfReference)\n        expr = self.expr\n        if expr.op() in self.memo.visit_memo:\n            return\n\n        stack = [expr]\n        seen = set()\n        memo = self.memo\n\n        while stack:\n            e = stack.pop()\n            op = e.op()\n\n            if op not in seen:\n                seen.add(op)\n\n                if isinstance(op, ops.PhysicalTable):\n                    memo.observe(e, self._format_table)\n                elif isinstance(op, ops.Node):\n                    stack.extend(\n                        arg\n                        for arg in reversed(op.args)\n                        if isinstance(arg, ir.Expr)\n                    )\n                    if isinstance(op, table_memo_ops):\n                        memo.observe(e, self._format_node)\n                elif isinstance(op, ops.TableNode) and op.has_schema():\n                    memo.observe(e, self._format_table)\n                memo.visit_memo.add(op)\n\n    def _indent(self, text, indents=1):\n        return util.indent(text, self.indent_size * indents)\n\n    def _format_table(self, expr):\n        table = expr.op()\n        # format the schema\n        rows = ['name: {}\\nschema:'.format(table.name)]\n        rows.extend(\n            map('  {} : {}'.format, table.schema.names, table.schema.types)\n        )\n        opname = type(table).__name__\n        type_display = self._get_type_display(expr)\n        opline = '{}[{}]'.format(opname, type_display)\n        return '{}\\n{}'.format(opline, self._indent('\\n'.join(rows)))\n\n    def _format_column(self, expr):\n        # HACK: if column is pulled from a Filter of another table, this parent\n        # will not be found in the memo\n        col = expr.op()\n        parent = col.parent()\n\n        if parent not in self.memo:\n            self.memo.observe(parent, formatter=self._format_node)\n\n        table_formatted = self.memo.get_alias(parent)\n        table_formatted = self._indent(table_formatted)\n\n        type_display = self._get_type_display(self.expr)\n        return \"Column[{0}] '{1}' from table\\n{2}\".format(\n            type_display, col.name, table_formatted\n        )\n\n    def _format_node(self, expr):\n        op = expr.op()\n        formatted_args = []\n\n        def visit(what, extra_indents=0):\n            if isinstance(what, ir.Expr):\n                result = self._format_subexpr(what)\n            else:\n                result = self._indent(str(what))\n\n            if extra_indents > 0:\n                result = util.indent(result, self.indent_size)\n\n            formatted_args.append(result)\n\n        arg_names = getattr(op, 'display_argnames', op.argnames)\n\n        if not arg_names:\n            for arg in op.flat_args():\n                visit(arg)\n        else:\n            signature = op.signature\n            arg_name_pairs = (\n                (arg, name)\n                for arg, name in zip(op.args, arg_names)\n                if signature[name].show\n            )\n            for arg, name in arg_name_pairs:\n                if name == 'arg' and isinstance(op, ops.ValueOp):\n                    # don't display first argument's name in repr\n                    name = None\n                if name is not None:\n                    name = self._indent('{}:'.format(name))\n                if util.is_iterable(arg):\n                    if name is not None and len(arg) > 0:\n                        formatted_args.append(name)\n                        indents = 1\n                    else:\n                        indents = 0\n                    for x in arg:\n                        visit(x, extra_indents=indents)\n                else:\n                    if name is not None:\n                        formatted_args.append(name)\n                        indents = 1\n                    else:\n                        indents = 0\n                    visit(arg, extra_indents=indents)\n\n        opname = type(op).__name__\n        type_display = self._get_type_display(expr)\n        opline = '{}[{}]'.format(opname, type_display)\n        return '\\n'.join([opline] + formatted_args)\n\n    def _format_subexpr(self, expr):\n        subexprs = self.memo.subexprs\n        key = expr._key\n        try:\n            result = subexprs[key]\n        except KeyError:\n            formatter = ExprFormatter(expr, memo=self.memo, memoize=False)\n            result = subexprs[key] = self._indent(formatter.get_result(), 1)\n        return result\n\n    def _get_type_display(self, expr=None):\n        if expr is None:\n            expr = self.expr\n        return expr._type_display()\n", "path": "ibis/expr/format.py"}]}