Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/io/formats/printing.py: 15%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

256 statements  

1""" 

2Printing tools. 

3""" 

4 

5from __future__ import annotations 

6 

7from collections.abc import ( 

8 Callable, 

9 Iterable, 

10 Mapping, 

11 Sequence, 

12) 

13import sys 

14from typing import ( 

15 TYPE_CHECKING, 

16 Any, 

17 TypeAlias, 

18 TypeVar, 

19) 

20from unicodedata import east_asian_width 

21 

22import numpy as np 

23 

24from pandas._config.config import _global_config as config 

25 

26from pandas.core.dtypes.generic import ( 

27 ABCExtensionArray, 

28 ABCIndex, 

29 ABCNDFrame, 

30) 

31from pandas.core.dtypes.inference import ( 

32 is_float, 

33 is_scalar, 

34) 

35from pandas.core.dtypes.missing import notna 

36 

37from pandas.io.formats.console import get_console_size 

38 

39if TYPE_CHECKING: 

40 from pandas._typing import ListLike 

41EscapeChars: TypeAlias = Mapping[str, str] | Iterable[str] 

42_KT = TypeVar("_KT") 

43_VT = TypeVar("_VT") 

44 

45 

46def adjoin(space: int, *lists: list[str], **kwargs: Any) -> str: 

47 """ 

48 Glues together two sets of strings using the amount of space requested. 

49 The idea is to prettify. 

50 

51 ---------- 

52 space : int 

53 number of spaces for padding 

54 lists : str 

55 list of str which being joined 

56 strlen : callable 

57 function used to calculate the length of each str. Needed for unicode 

58 handling. 

59 justfunc : callable 

60 function used to justify str. Needed for unicode handling. 

61 """ 

62 strlen = kwargs.pop("strlen", len) 

63 justfunc = kwargs.pop("justfunc", _adj_justify) 

64 

65 newLists = [] 

66 lengths = [max(map(strlen, x)) + space for x in lists[:-1]] 

67 # not the last one 

68 lengths.append(max(map(len, lists[-1]))) 

69 maxLen = max(map(len, lists)) 

70 for i, lst in enumerate(lists): 

71 nl = justfunc(lst, lengths[i], mode="left") 

72 nl = ([" " * lengths[i]] * (maxLen - len(lst))) + nl 

73 newLists.append(nl) 

74 toJoin = zip(*newLists, strict=True) 

75 return "\n".join("".join(lines) for lines in toJoin) 

76 

77 

78def _adj_justify(texts: Iterable[str], max_len: int, mode: str = "right") -> list[str]: 

79 """ 

80 Perform ljust, center, rjust against string or list-like 

81 """ 

82 if mode == "left": 

83 return [x.ljust(max_len) for x in texts] 

84 elif mode == "center": 

85 return [x.center(max_len) for x in texts] 

86 else: 

87 return [x.rjust(max_len) for x in texts] 

88 

89 

90# Unicode consolidation 

91# --------------------- 

92# 

93# pprinting utility functions for generating Unicode text or 

94# bytes(3.x)/str(2.x) representations of objects. 

95# Try to use these as much as possible rather than rolling your own. 

96# 

97# When to use 

98# ----------- 

99# 

100# 1) If you're writing code internal to pandas (no I/O directly involved), 

101# use pprint_thing(). 

102# 

103# It will always return unicode text which can handled by other 

104# parts of the package without breakage. 

105# 

106# 2) if you need to write something out to file, use 

107# pprint_thing_encoded(encoding). 

108# 

109# If no encoding is specified, it defaults to utf-8. Since encoding pure 

110# ascii with utf-8 is a no-op you can safely use the default utf-8 if you're 

111# working with straight ascii. 

112 

113 

114def _pprint_seq( 

115 seq: ListLike, _nest_lvl: int = 0, max_seq_items: int | None = None, **kwds: Any 

116) -> str: 

117 """ 

118 internal. pprinter for iterables. you should probably use pprint_thing() 

119 rather than calling this directly. 

120 

121 bounds length of printed sequence, depending on options 

122 """ 

123 if isinstance(seq, set): 

124 fmt = "{{{body}}}" 

125 elif isinstance(seq, frozenset): 

126 fmt = "frozenset()" if not seq else "frozenset({{{body}}})" 

127 else: 

128 fmt = "[{body}]" if hasattr(seq, "__setitem__") else "({body})" 

129 

130 if max_seq_items is False: 

131 max_items = None 

132 else: 

133 max_items = max_seq_items or config["display"]["max_seq_items"] or len(seq) 

134 

135 s = iter(seq) 

136 # handle sets, no slicing 

137 r = [] 

138 max_items_reached = False 

139 for i, item in enumerate(s): 

140 if (max_items is not None) and (i >= max_items): 

141 max_items_reached = True 

142 break 

143 if is_float(item) and notna(item): 

144 # GH#60503 

145 from pandas.io.formats.format import _trim_zeros_single_float 

146 

147 precision = config["display"]["precision"] 

148 item = _trim_zeros_single_float(f"{item:.{precision}f}") 

149 r.append(pprint_thing(item, _nest_lvl + 1, max_seq_items=max_seq_items, **kwds)) 

150 body = ", ".join(r) 

151 

152 if max_items_reached: 

153 body += ", ..." 

154 elif isinstance(seq, tuple) and len(seq) == 1: 

155 body += "," 

156 

157 return fmt.format(body=body) 

158 

159 

160def _pprint_dict( 

161 seq: Mapping[Any, Any], 

162 _nest_lvl: int = 0, 

163 max_seq_items: int | None = None, 

164 **kwds: Any, 

165) -> str: 

166 """ 

167 internal. pprinter for iterables. you should probably use pprint_thing() 

168 rather than calling this directly. 

169 """ 

170 fmt = "{{{things}}}" 

171 pairs = [] 

172 

173 pfmt = "{key}: {val}" 

174 

175 if max_seq_items is False: 

176 nitems = len(seq) 

177 else: 

178 nitems = max_seq_items or config["display"]["max_seq_items"] or len(seq) 

179 

180 for k, v in list(seq.items())[:nitems]: 

181 pairs.append( 

182 pfmt.format( 

183 key=pprint_thing(k, _nest_lvl + 1, max_seq_items=max_seq_items, **kwds), 

184 val=pprint_thing(v, _nest_lvl + 1, max_seq_items=max_seq_items, **kwds), 

185 ) 

186 ) 

187 

188 if nitems < len(seq): 

189 return fmt.format(things=", ".join(pairs) + ", ...") 

190 else: 

191 return fmt.format(things=", ".join(pairs)) 

192 

193 

194def pprint_thing( 

195 thing: object, 

196 _nest_lvl: int = 0, 

197 escape_chars: EscapeChars | None = None, 

198 default_escapes: bool = False, 

199 quote_strings: bool = False, 

200 max_seq_items: int | None = None, 

201) -> str: 

202 """ 

203 This function is the sanctioned way of converting objects 

204 to a string representation and properly handles nested sequences. 

205 

206 Parameters 

207 ---------- 

208 thing : anything to be formatted 

209 _nest_lvl : internal use only. pprint_thing() is mutually-recursive 

210 with pprint_sequence, this argument is used to keep track of the 

211 current nesting level, and limit it. 

212 escape_chars : list[str] or Mapping[str, str], optional 

213 Characters to escape. If a Mapping is passed the values are the 

214 replacements 

215 default_escapes : bool, default False 

216 Whether the input escape characters replaces or adds to the defaults 

217 max_seq_items : int or None, default None 

218 Pass through to other pretty printers to limit sequence printing 

219 

220 Returns 

221 ------- 

222 str 

223 """ 

224 

225 # TODO: should is_scalar exclude np.record? 

226 if is_scalar(thing) and not isinstance(thing, np.record): 

227 # GH#58285 put this check before Mapping check for performance 

228 result = _as_escaped_string(thing, escape_chars, default_escapes) 

229 if quote_strings and isinstance(thing, str): 

230 result = f"'{result}'" 

231 elif hasattr(thing, "__next__"): 

232 return str(thing) 

233 elif ( 

234 isinstance(thing, Mapping) 

235 and _nest_lvl < config["display"]["pprint_nest_depth"] 

236 ): 

237 result = _pprint_dict( 

238 thing, _nest_lvl, quote_strings=True, max_seq_items=max_seq_items 

239 ) 

240 elif ( 

241 # GH#61809 Only iterate over types where element-by-element formatting 

242 # is appropriate. Third-party array-like objects (e.g. xarray DataArray) 

243 # can be extremely expensive to iterate and should use their own repr. 

244 isinstance( 

245 thing, 

246 ( 

247 np.ndarray, 

248 np.void, 

249 list, 

250 tuple, 

251 set, 

252 frozenset, 

253 range, 

254 ABCExtensionArray, 

255 ABCIndex, 

256 ABCNDFrame, 

257 ), 

258 ) 

259 # GH#64638 0-d arrays are not iterable; fall through to str() 

260 and not (isinstance(thing, np.ndarray) and thing.ndim == 0) 

261 and _nest_lvl < config["display"]["pprint_nest_depth"] 

262 ): 

263 result = _pprint_seq( 

264 # error: Argument 1 to "_pprint_seq" has incompatible type "object"; 

265 # expected "ExtensionArray | ndarray[Any, Any] | Index | Series | 

266 # SequenceNotStr[Any] | range" 

267 thing, # type: ignore[arg-type] 

268 _nest_lvl, 

269 escape_chars=escape_chars, 

270 quote_strings=quote_strings, 

271 max_seq_items=max_seq_items, 

272 ) 

273 else: 

274 result = _as_escaped_string(thing, escape_chars, default_escapes) 

275 

276 return result 

277 

278 

279def _as_escaped_string( 

280 thing: Any, escape_chars: EscapeChars | None, default_escapes: bool 

281) -> str: 

282 translate = {"\t": r"\t", "\n": r"\n", "\r": r"\r", "'": r"\'"} 

283 if isinstance(escape_chars, Mapping): 

284 if default_escapes: 

285 translate.update(escape_chars) 

286 else: 

287 translate = escape_chars # type: ignore[assignment] 

288 escape_chars = list(escape_chars.keys()) 

289 else: 

290 escape_chars = escape_chars or () 

291 

292 result = str(thing) 

293 for c in escape_chars: 

294 result = result.replace(c, translate[c]) 

295 return result 

296 

297 

298def pprint_thing_encoded( 

299 thing: object, encoding: str = "utf-8", errors: str = "replace" 

300) -> bytes: 

301 value = pprint_thing(thing) # get unicode representation of thing 

302 return value.encode(encoding, errors) 

303 

304 

305def enable_data_resource_formatter(enable: bool) -> None: 

306 if "IPython" not in sys.modules: 

307 # definitely not in IPython 

308 return 

309 from IPython import get_ipython # pyright: ignore[reportPrivateImportUsage] 

310 

311 # error: Call to untyped function "get_ipython" in typed context 

312 ip = get_ipython() 

313 if ip is None: 

314 # still not in IPython 

315 return 

316 

317 formatters = ip.display_formatter.formatters 

318 mimetype = "application/vnd.dataresource+json" 

319 

320 if enable: 

321 if mimetype not in formatters: 

322 # define tableschema formatter 

323 from IPython.core.formatters import BaseFormatter 

324 from traitlets.traitlets import ObjectName 

325 

326 class TableSchemaFormatter(BaseFormatter): 

327 print_method = ObjectName("_repr_data_resource_") 

328 _return_type = (dict,) 

329 

330 # register it: 

331 formatters[mimetype] = TableSchemaFormatter() 

332 # enable it if it's been disabled: 

333 formatters[mimetype].enabled = True 

334 # unregister tableschema mime-type 

335 elif mimetype in formatters: 

336 formatters[mimetype].enabled = False 

337 

338 

339def default_pprint(thing: Any, max_seq_items: int | None = None) -> str: 

340 return pprint_thing( 

341 thing, 

342 escape_chars=("\t", "\r", "\n"), 

343 quote_strings=True, 

344 max_seq_items=max_seq_items, 

345 ) 

346 

347 

348def format_object_summary( 

349 obj: ListLike, 

350 formatter: Callable[..., Any], 

351 is_justify: bool = True, 

352 name: str | None = None, 

353 indent_for_name: bool = True, 

354 line_break_each_value: bool = False, 

355) -> str: 

356 """ 

357 Return the formatted obj as a unicode string 

358 

359 Parameters 

360 ---------- 

361 obj : object 

362 must be iterable and support __getitem__ 

363 formatter : callable 

364 string formatter for an element 

365 is_justify : bool 

366 should justify the display 

367 name : name, optional 

368 defaults to the class name of the obj 

369 indent_for_name : bool, default True 

370 Whether subsequent lines should be indented to 

371 align with the name. 

372 line_break_each_value : bool, default False 

373 If True, inserts a line break for each value of ``obj``. 

374 If False, only break lines when a line of values gets wider 

375 than the display width. 

376 

377 Returns 

378 ------- 

379 summary string 

380 """ 

381 display_width, _ = get_console_size() 

382 if display_width is None: 

383 display_width = config["display"]["width"] or 80 

384 if name is None: 

385 name = type(obj).__name__ 

386 

387 if indent_for_name: 

388 name_len = len(name) 

389 space1 = f"\n{(' ' * (name_len + 1))}" 

390 space2 = f"\n{(' ' * (name_len + 2))}" 

391 else: 

392 space1 = "\n" 

393 space2 = "\n " # space for the opening '[' 

394 

395 n = len(obj) 

396 if line_break_each_value: 

397 # If we want to vertically align on each value of obj, we need to 

398 # separate values by a line break and indent the values 

399 sep = ",\n " + " " * len(name) 

400 else: 

401 sep = "," 

402 max_seq_items = config["display"]["max_seq_items"] or n 

403 

404 # are we a truncated display 

405 is_truncated = n > max_seq_items 

406 

407 # adj can optionally handle unicode eastern asian width 

408 adj = get_adjustment() 

409 

410 def _extend_line( 

411 s: str, line: str, value: str, display_width: int, next_line_prefix: str 

412 ) -> tuple[str, str]: 

413 if adj.len(line.rstrip()) + adj.len(value.rstrip()) >= display_width: 

414 s += line.rstrip() 

415 line = next_line_prefix 

416 line += value 

417 return s, line 

418 

419 def best_len(values: list[str]) -> int: 

420 if values: 

421 return max(adj.len(x) for x in values) 

422 else: 

423 return 0 

424 

425 close = ", " 

426 

427 if n == 0: 

428 summary = f"[]{close}" 

429 elif n == 1 and not line_break_each_value: 

430 first = formatter(obj[0]) 

431 summary = f"[{first}]{close}" 

432 else: 

433 if max_seq_items == 1: 

434 # If max_seq_items=1 show only last element 

435 head = [] 

436 tail = [formatter(x) for x in obj[-1:]] 

437 elif n > max_seq_items: 

438 n = min(max_seq_items // 2, 10) 

439 head = [formatter(x) for x in obj[:n]] 

440 tail = [formatter(x) for x in obj[-n:]] 

441 else: 

442 head = [] 

443 tail = [formatter(x) for x in obj] 

444 

445 # adjust all values to max length if needed 

446 if is_justify: 

447 if line_break_each_value: 

448 # Justify each string in the values of head and tail, so the 

449 # strings will right align when head and tail are stacked 

450 # vertically. 

451 head, tail = _justify(head, tail) 

452 elif is_truncated or not ( 

453 len(", ".join(head)) < display_width 

454 and len(", ".join(tail)) < display_width 

455 ): 

456 # Each string in head and tail should align with each other 

457 max_length = max(best_len(head), best_len(tail)) 

458 head = [x.rjust(max_length) for x in head] 

459 tail = [x.rjust(max_length) for x in tail] 

460 # If we are not truncated and we are only a single 

461 # line, then don't justify 

462 

463 if line_break_each_value: 

464 # Now head and tail are of type List[Tuple[str]]. Below we 

465 # convert them into List[str], so there will be one string per 

466 # value. Also truncate items horizontally if wider than 

467 # max_space 

468 max_space = display_width - len(space2) 

469 value = tail[0] 

470 max_items = 1 

471 for num_items in reversed(range(1, len(value) + 1)): 

472 pprinted_seq = _pprint_seq(value, max_seq_items=num_items) 

473 if len(pprinted_seq) < max_space: 

474 max_items = num_items 

475 break 

476 head = [_pprint_seq(x, max_seq_items=max_items) for x in head] 

477 tail = [_pprint_seq(x, max_seq_items=max_items) for x in tail] 

478 

479 summary = "" 

480 line = space2 

481 

482 for head_value in head: 

483 word = head_value + sep + " " 

484 summary, line = _extend_line(summary, line, word, display_width, space2) 

485 

486 if is_truncated: 

487 # remove trailing space of last line 

488 summary += line.rstrip() + space2 + "..." 

489 line = space2 

490 

491 for tail_item in tail[:-1]: 

492 word = tail_item + sep + " " 

493 summary, line = _extend_line(summary, line, word, display_width, space2) 

494 

495 # last value: no sep added + 1 space of width used for trailing ',' 

496 summary, line = _extend_line(summary, line, tail[-1], display_width - 2, space2) 

497 summary += line 

498 

499 # right now close is either '' or ', ' 

500 # Now we want to include the ']', but not the maybe space. 

501 close = "]" + close.rstrip(" ") 

502 summary += close 

503 

504 if len(summary) > (display_width) or line_break_each_value: 

505 summary += space1 

506 else: # one row 

507 summary += " " 

508 

509 # remove initial space 

510 summary = "[" + summary[len(space2) :] 

511 

512 return summary 

513 

514 

515def _justify( 

516 head: list[Sequence[str]], tail: list[Sequence[str]] 

517) -> tuple[list[tuple[str, ...]], list[tuple[str, ...]]]: 

518 """ 

519 Justify items in head and tail, so they are right-aligned when stacked. 

520 

521 Parameters 

522 ---------- 

523 head : list-like of list-likes of strings 

524 tail : list-like of list-likes of strings 

525 

526 Returns 

527 ------- 

528 tuple of list of tuples of strings 

529 Same as head and tail, but items are right aligned when stacked 

530 vertically. 

531 

532 Examples 

533 -------- 

534 >>> _justify([["a", "b"]], [["abc", "abcd"]]) 

535 ([(' a', ' b')], [('abc', 'abcd')]) 

536 """ 

537 combined = head + tail 

538 

539 # For each position for the sequences in ``combined``, 

540 # find the length of the largest string. 

541 max_length = [0] * len(combined[0]) 

542 for inner_seq in combined: 

543 length = [len(item) for item in inner_seq] 

544 max_length = [max(x, y) for x, y in zip(max_length, length, strict=True)] 

545 

546 # justify each item in each list-like in head and tail using max_length 

547 head_tuples = [ 

548 tuple(x.rjust(max_len) for x, max_len in zip(seq, max_length, strict=True)) 

549 for seq in head 

550 ] 

551 tail_tuples = [ 

552 tuple(x.rjust(max_len) for x, max_len in zip(seq, max_length, strict=True)) 

553 for seq in tail 

554 ] 

555 return head_tuples, tail_tuples 

556 

557 

558class PrettyDict(dict[_KT, _VT]): 

559 """Dict extension to support abbreviated __repr__""" 

560 

561 def __repr__(self) -> str: 

562 return pprint_thing(self) 

563 

564 

565class _TextAdjustment: 

566 def __init__(self) -> None: 

567 self.encoding = config["display"]["encoding"] 

568 

569 def len(self, text: str) -> int: 

570 return len(text) 

571 

572 def justify(self, texts: Any, max_len: int, mode: str = "right") -> list[str]: 

573 """ 

574 Perform ljust, center, rjust against string or list-like 

575 """ 

576 if mode == "left": 

577 return [x.ljust(max_len) for x in texts] 

578 elif mode == "center": 

579 return [x.center(max_len) for x in texts] 

580 else: 

581 return [x.rjust(max_len) for x in texts] 

582 

583 def adjoin(self, space: int, *lists: Any, **kwargs: Any) -> str: 

584 return adjoin(space, *lists, strlen=self.len, justfunc=self.justify, **kwargs) 

585 

586 

587class _EastAsianTextAdjustment(_TextAdjustment): 

588 def __init__(self) -> None: 

589 super().__init__() 

590 if config["display"]["unicode"]["ambiguous_as_wide"]: 

591 self.ambiguous_width = 2 

592 else: 

593 self.ambiguous_width = 1 

594 

595 # Definition of East Asian Width 

596 # https://unicode.org/reports/tr11/ 

597 # Ambiguous width can be changed by option 

598 self._EAW_MAP = {"Na": 1, "N": 1, "W": 2, "F": 2, "H": 1} 

599 

600 def len(self, text: str) -> int: 

601 """ 

602 Calculate display width considering unicode East Asian Width 

603 """ 

604 if not isinstance(text, str): 

605 return len(text) 

606 

607 return sum( 

608 self._EAW_MAP.get(east_asian_width(c), self.ambiguous_width) for c in text 

609 ) 

610 

611 def justify( 

612 self, texts: Iterable[str], max_len: int, mode: str = "right" 

613 ) -> list[str]: 

614 # re-calculate padding space per str considering East Asian Width 

615 def _get_pad(t: str) -> int: 

616 return max_len - self.len(t) + len(t) 

617 

618 if mode == "left": 

619 return [x.ljust(_get_pad(x)) for x in texts] 

620 elif mode == "center": 

621 return [x.center(_get_pad(x)) for x in texts] 

622 else: 

623 return [x.rjust(_get_pad(x)) for x in texts] 

624 

625 

626def get_adjustment() -> _TextAdjustment: 

627 use_east_asian_width = config["display"]["unicode"]["east_asian_width"] 

628 if use_east_asian_width: 

629 return _EastAsianTextAdjustment() 

630 else: 

631 return _TextAdjustment()