Skip to content

Commit f9b5d08

Browse files
authored
Fix exceptions in whos magic command (#14970)
Correction for handling for Pandas and Polars DataFrame and Series types for the %whos magic command. This small change reverts the most recent changes to **namespace.py**, and relative to IPython version 8.26.0 (from my Python 3.12 install), simply adds a two-line **elif** clause to specifically handle the case of **DataFrame** and **Series** types, as found in Pandas and Polars. The existing code looks too generally for variables with 'shape' and '\_\_len\_\_' attributes, which unintentionally catches some modules and other items, and causes exceptions. It also unintentionally hijacks correct 'default' handling of strings and other types. The change here is focused directly and only on the **DataFrame** and **Series** types, and if found, shows their shape attribute. Processing of all other types is left unchanged from 8.26.0. Please see discussion at Issue #14952. Note, this is my first real GitHub pull request, so please let me know if I've done anything incorrectly, or left out any steps, as I would like to learn from this experience!
2 parents 1460cef + cf7f60b commit f9b5d08

File tree

2 files changed

+50
-8
lines changed

2 files changed

+50
-8
lines changed

IPython/core/magics/namespace.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,7 @@ def whos(self, parameter_s=''):
361361
- For numpy arrays, a summary with shape, number of
362362
elements, typecode and size in memory.
363363
364-
- For objects that have shape attribute, primarily dataframe and series like
365-
objects, will print the shape.
364+
- For DataFrame and Series types: their shape.
366365
367366
- Everything else: a string representation, snipping their middle if
368367
too long.
@@ -450,7 +449,9 @@ def type_name(v):
450449
Mb = 1048576 # kb**2
451450
for vname,var,vtype in zip(varnames,varlist,typelist):
452451
print(vformat.format(vname, vtype, varwidth=varwidth, typewidth=typewidth), end=' ')
453-
if vtype == ndarray_type:
452+
if vtype in seq_types:
453+
print("n="+str(len(var)))
454+
elif vtype == ndarray_type:
454455
vshape = str(var.shape).replace(',','').replace(' ','x')[1:-1]
455456
if vtype==ndarray_type:
456457
# numpy
@@ -463,16 +464,13 @@ def type_name(v):
463464
else:
464465
print(aformat % (vshape, vsize, vdtype, vbytes), end=' ')
465466
if vbytes < Mb:
466-
print('(%s kb)' % (vbytes/kb,))
467+
print("(%s kb)" % (vbytes / kb,))
467468
else:
468469
print("(%s Mb)" % (vbytes / Mb,))
469-
elif hasattr(var, "shape"):
470+
elif vtype in ["DataFrame", "Series"]:
470471
# Useful for DataFrames and Series
471472
# Ought to work for both pandas and polars
472473
print(f"Shape: {var.shape}")
473-
elif hasattr(var, "__len__") and not isinstance(var, str):
474-
## types that can be used in len function
475-
print(f"n={len(var)}")
476474
else:
477475
try:
478476
vstr = str(var)

tests/test_magic.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
"""Tests for various magic functions."""
33

4+
import collections
45
import gc
56
import io
67
import json
@@ -768,6 +769,49 @@ def test_whos_longstr(input, expected):
768769
assert expected == from_whos
769770

770771

772+
def test_whos_len_namedtuple():
773+
_ip = get_ipython()
774+
_ip.run_line_magic("reset", "-f")
775+
_ip.user_ns["alpha"] = 123
776+
_ip.user_ns["beta"] = "test"
777+
_ip.user_ns["nt"] = collections.namedtuple("MyNamedTuple", "col1 col2")
778+
_ip.user_ns["x"] = _ip.user_ns["nt"]("a", "b")
779+
expected = (
780+
"Variable Type Data/Info\n"
781+
"------------------------------------\n"
782+
"alpha int 123\n"
783+
"beta str test\n"
784+
"nt type <class 'tests.test_magic.MyNamedTuple'>\n"
785+
"x MyNamedTuple MyNamedTuple(col1='a', col2='b')\n"
786+
)
787+
with capture_output() as captured:
788+
ip.run_line_magic("whos", "")
789+
stdout = captured.stdout
790+
assert stdout == expected.strip() + "\n"
791+
792+
793+
@dec.skip_without("pandas")
794+
def test_whos_len_pandas():
795+
import pandas as pd
796+
797+
_ip = get_ipython()
798+
_ip.run_line_magic("reset", "-f")
799+
df = pd.DataFrame({"a": range(10), "b": range(10, 20)})
800+
_ip.user_ns["df"] = df
801+
s = df["a"]
802+
_ip.user_ns["s"] = s
803+
expected = (
804+
"Variable Type Data/Info\n"
805+
"---------------------------------\n"
806+
"df DataFrame Shape: (10, 2)\n"
807+
"s Series Shape: (10,)\n"
808+
)
809+
with capture_output() as captured:
810+
ip.run_line_magic("whos", "")
811+
stdout = captured.stdout
812+
assert stdout == expected.strip() + "\n"
813+
814+
771815
def doctest_precision():
772816
"""doctest for %precision
773817

0 commit comments

Comments
 (0)