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

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

251 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 

22from pandas._config import get_option 

23 

24from pandas.core.dtypes.inference import is_sequence 

25 

26from pandas.io.formats.console import get_console_size 

27 

28if TYPE_CHECKING: 

29 from pandas._typing import ListLike 

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

31_KT = TypeVar("_KT") 

32_VT = TypeVar("_VT") 

33 

34 

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

36 """ 

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

38 The idea is to prettify. 

39 

40 ---------- 

41 space : int 

42 number of spaces for padding 

43 lists : str 

44 list of str which being joined 

45 strlen : callable 

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

47 handling. 

48 justfunc : callable 

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

50 """ 

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

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

53 

54 newLists = [] 

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

56 # not the last one 

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

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

59 for i, lst in enumerate(lists): 

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

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

62 newLists.append(nl) 

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

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

65 

66 

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

68 """ 

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

70 """ 

71 if mode == "left": 

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

73 elif mode == "center": 

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

75 else: 

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

77 

78 

79# Unicode consolidation 

80# --------------------- 

81# 

82# pprinting utility functions for generating Unicode text or 

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

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

85# 

86# When to use 

87# ----------- 

88# 

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

90# use pprint_thing(). 

91# 

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

93# parts of the package without breakage. 

94# 

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

96# pprint_thing_encoded(encoding). 

97# 

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

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

100# working with straight ascii. 

101 

102 

103def _pprint_seq( 

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

105) -> str: 

106 """ 

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

108 rather than calling this directly. 

109 

110 bounds length of printed sequence, depending on options 

111 """ 

112 if isinstance(seq, set): 

113 fmt = "{{{body}}}" 

114 elif isinstance(seq, frozenset): 

115 fmt = "frozenset({{{body}}})" 

116 else: 

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

118 

119 if max_seq_items is False: 

120 max_items = None 

121 else: 

122 max_items = max_seq_items or get_option("max_seq_items") or len(seq) 

123 

124 s = iter(seq) 

125 # handle sets, no slicing 

126 r = [] 

127 max_items_reached = False 

128 for i, item in enumerate(s): 

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

130 max_items_reached = True 

131 break 

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

133 body = ", ".join(r) 

134 

135 if max_items_reached: 

136 body += ", ..." 

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

138 body += "," 

139 

140 return fmt.format(body=body) 

141 

142 

143def _pprint_dict( 

144 seq: Mapping, _nest_lvl: int = 0, max_seq_items: int | None = None, **kwds: Any 

145) -> str: 

146 """ 

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

148 rather than calling this directly. 

149 """ 

150 fmt = "{{{things}}}" 

151 pairs = [] 

152 

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

154 

155 if max_seq_items is False: 

156 nitems = len(seq) 

157 else: 

158 nitems = max_seq_items or get_option("max_seq_items") or len(seq) 

159 

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

161 pairs.append( 

162 pfmt.format( 

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

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

165 ) 

166 ) 

167 

168 if nitems < len(seq): 

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

170 else: 

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

172 

173 

174def pprint_thing( 

175 thing: object, 

176 _nest_lvl: int = 0, 

177 escape_chars: EscapeChars | None = None, 

178 default_escapes: bool = False, 

179 quote_strings: bool = False, 

180 max_seq_items: int | None = None, 

181) -> str: 

182 """ 

183 This function is the sanctioned way of converting objects 

184 to a string representation and properly handles nested sequences. 

185 

186 Parameters 

187 ---------- 

188 thing : anything to be formatted 

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

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

191 current nesting level, and limit it. 

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

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

194 replacements 

195 default_escapes : bool, default False 

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

197 max_seq_items : int or None, default None 

198 Pass through to other pretty printers to limit sequence printing 

199 

200 Returns 

201 ------- 

202 str 

203 """ 

204 

205 def as_escaped_string( 

206 thing: Any, escape_chars: EscapeChars | None = escape_chars 

207 ) -> str: 

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

209 if isinstance(escape_chars, Mapping): 

210 if default_escapes: 

211 translate.update(escape_chars) 

212 else: 

213 translate = escape_chars # type: ignore[assignment] 

214 escape_chars = list(escape_chars.keys()) 

215 else: 

216 escape_chars = escape_chars or () 

217 

218 result = str(thing) 

219 for c in escape_chars: 

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

221 return result 

222 

223 if hasattr(thing, "__next__"): 

224 return str(thing) 

225 elif isinstance(thing, Mapping) and _nest_lvl < get_option( 

226 "display.pprint_nest_depth" 

227 ): 

228 result = _pprint_dict( 

229 thing, _nest_lvl, quote_strings=True, max_seq_items=max_seq_items 

230 ) 

231 elif is_sequence(thing) and _nest_lvl < get_option("display.pprint_nest_depth"): 

232 result = _pprint_seq( 

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

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

235 # SequenceNotStr[Any] | range" 

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

237 _nest_lvl, 

238 escape_chars=escape_chars, 

239 quote_strings=quote_strings, 

240 max_seq_items=max_seq_items, 

241 ) 

242 elif isinstance(thing, str) and quote_strings: 

243 result = f"'{as_escaped_string(thing)}'" 

244 else: 

245 result = as_escaped_string(thing) 

246 

247 return result 

248 

249 

250def pprint_thing_encoded( 

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

252) -> bytes: 

253 value = pprint_thing(object) # get unicode representation of object 

254 return value.encode(encoding, errors) 

255 

256 

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

258 if "IPython" not in sys.modules: 

259 # definitely not in IPython 

260 return 

261 from IPython import get_ipython 

262 

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

264 ip = get_ipython() # type: ignore[no-untyped-call] 

265 if ip is None: 

266 # still not in IPython 

267 return 

268 

269 formatters = ip.display_formatter.formatters 

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

271 

272 if enable: 

273 if mimetype not in formatters: 

274 # define tableschema formatter 

275 from IPython.core.formatters import BaseFormatter 

276 from traitlets import ObjectName 

277 

278 class TableSchemaFormatter(BaseFormatter): 

279 print_method = ObjectName("_repr_data_resource_") 

280 _return_type = (dict,) 

281 

282 # register it: 

283 formatters[mimetype] = TableSchemaFormatter() 

284 # enable it if it's been disabled: 

285 formatters[mimetype].enabled = True 

286 # unregister tableschema mime-type 

287 elif mimetype in formatters: 

288 formatters[mimetype].enabled = False 

289 

290 

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

292 return pprint_thing( 

293 thing, 

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

295 quote_strings=True, 

296 max_seq_items=max_seq_items, 

297 ) 

298 

299 

300def format_object_summary( 

301 obj: ListLike, 

302 formatter: Callable, 

303 is_justify: bool = True, 

304 name: str | None = None, 

305 indent_for_name: bool = True, 

306 line_break_each_value: bool = False, 

307) -> str: 

308 """ 

309 Return the formatted obj as a unicode string 

310 

311 Parameters 

312 ---------- 

313 obj : object 

314 must be iterable and support __getitem__ 

315 formatter : callable 

316 string formatter for an element 

317 is_justify : bool 

318 should justify the display 

319 name : name, optional 

320 defaults to the class name of the obj 

321 indent_for_name : bool, default True 

322 Whether subsequent lines should be indented to 

323 align with the name. 

324 line_break_each_value : bool, default False 

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

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

327 than the display width. 

328 

329 Returns 

330 ------- 

331 summary string 

332 """ 

333 display_width, _ = get_console_size() 

334 if display_width is None: 

335 display_width = get_option("display.width") or 80 

336 if name is None: 

337 name = type(obj).__name__ 

338 

339 if indent_for_name: 

340 name_len = len(name) 

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

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

343 else: 

344 space1 = "\n" 

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

346 

347 n = len(obj) 

348 if line_break_each_value: 

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

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

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

352 else: 

353 sep = "," 

354 max_seq_items = get_option("display.max_seq_items") or n 

355 

356 # are we a truncated display 

357 is_truncated = n > max_seq_items 

358 

359 # adj can optionally handle unicode eastern asian width 

360 adj = get_adjustment() 

361 

362 def _extend_line( 

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

364 ) -> tuple[str, str]: 

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

366 s += line.rstrip() 

367 line = next_line_prefix 

368 line += value 

369 return s, line 

370 

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

372 if values: 

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

374 else: 

375 return 0 

376 

377 close = ", " 

378 

379 if n == 0: 

380 summary = f"[]{close}" 

381 elif n == 1 and not line_break_each_value: 

382 first = formatter(obj[0]) 

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

384 elif n == 2 and not line_break_each_value: 

385 first = formatter(obj[0]) 

386 last = formatter(obj[-1]) 

387 summary = f"[{first}, {last}]{close}" 

388 else: 

389 if max_seq_items == 1: 

390 # If max_seq_items=1 show only last element 

391 head = [] 

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

393 elif n > max_seq_items: 

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

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

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

397 else: 

398 head = [] 

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

400 

401 # adjust all values to max length if needed 

402 if is_justify: 

403 if line_break_each_value: 

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

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

406 # vertically. 

407 head, tail = _justify(head, tail) 

408 elif is_truncated or not ( 

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

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

411 ): 

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

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

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

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

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

417 # line, then don't justify 

418 

419 if line_break_each_value: 

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

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

422 # value. Also truncate items horizontally if wider than 

423 # max_space 

424 max_space = display_width - len(space2) 

425 value = tail[0] 

426 max_items = 1 

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

428 pprinted_seq = _pprint_seq(value, max_seq_items=num_items) 

429 if len(pprinted_seq) < max_space: 

430 max_items = num_items 

431 break 

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

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

434 

435 summary = "" 

436 line = space2 

437 

438 for head_value in head: 

439 word = head_value + sep + " " 

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

441 

442 if is_truncated: 

443 # remove trailing space of last line 

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

445 line = space2 

446 

447 for tail_item in tail[:-1]: 

448 word = tail_item + sep + " " 

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

450 

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

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

453 summary += line 

454 

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

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

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

458 summary += close 

459 

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

461 summary += space1 

462 else: # one row 

463 summary += " " 

464 

465 # remove initial space 

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

467 

468 return summary 

469 

470 

471def _justify( 

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

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

474 """ 

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

476 

477 Parameters 

478 ---------- 

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

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

481 

482 Returns 

483 ------- 

484 tuple of list of tuples of strings 

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

486 vertically. 

487 

488 Examples 

489 -------- 

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

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

492 """ 

493 combined = head + tail 

494 

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

496 # find the length of the largest string. 

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

498 for inner_seq in combined: 

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

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

501 

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

503 head_tuples = [ 

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

505 for seq in head 

506 ] 

507 tail_tuples = [ 

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

509 for seq in tail 

510 ] 

511 return head_tuples, tail_tuples 

512 

513 

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

515 """Dict extension to support abbreviated __repr__""" 

516 

517 def __repr__(self) -> str: 

518 return pprint_thing(self) 

519 

520 

521class _TextAdjustment: 

522 def __init__(self) -> None: 

523 self.encoding = get_option("display.encoding") 

524 

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

526 return len(text) 

527 

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

529 """ 

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

531 """ 

532 if mode == "left": 

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

534 elif mode == "center": 

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

536 else: 

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

538 

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

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

541 

542 

543class _EastAsianTextAdjustment(_TextAdjustment): 

544 def __init__(self) -> None: 

545 super().__init__() 

546 if get_option("display.unicode.ambiguous_as_wide"): 

547 self.ambiguous_width = 2 

548 else: 

549 self.ambiguous_width = 1 

550 

551 # Definition of East Asian Width 

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

553 # Ambiguous width can be changed by option 

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

555 

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

557 """ 

558 Calculate display width considering unicode East Asian Width 

559 """ 

560 if not isinstance(text, str): 

561 return len(text) 

562 

563 return sum( 

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

565 ) 

566 

567 def justify( 

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

569 ) -> list[str]: 

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

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

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

573 

574 if mode == "left": 

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

576 elif mode == "center": 

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

578 else: 

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

580 

581 

582def get_adjustment() -> _TextAdjustment: 

583 use_east_asian_width = get_option("display.unicode.east_asian_width") 

584 if use_east_asian_width: 

585 return _EastAsianTextAdjustment() 

586 else: 

587 return _TextAdjustment()