1"""Calendar printing functions 2 3Note when comparing these calendars to the ones printed by cal(1): By 4default, these calendars have Monday as the first day of the week, and 5Sunday as the last (the European convention). Use setfirstweekday() to 6set the first day of the week (0=Monday, 6=Sunday).""" 7 8import sys 9import datetime 10import locale as _locale 11from itertools import repeat 12 13__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday", 14 "firstweekday", "isleap", "leapdays", "weekday", "monthrange", 15 "monthcalendar", "prmonth", "month", "prcal", "calendar", 16 "timegm", "month_name", "month_abbr", "day_name", "day_abbr", 17 "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar", 18 "LocaleHTMLCalendar", "weekheader", 19 "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", 20 "SATURDAY", "SUNDAY"] 21 22# Exception raised for bad input (with string parameter for details) 23error = ValueError 24 25# Exceptions raised for bad input 26class IllegalMonthError(ValueError): 27 def __init__(self, month): 28 self.month = month 29 def __str__(self): 30 return "bad month number %r; must be 1-12" % self.month 31 32 33class IllegalWeekdayError(ValueError): 34 def __init__(self, weekday): 35 self.weekday = weekday 36 def __str__(self): 37 return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday 38 39 40# Constants for months referenced later 41January = 1 42February = 2 43 44# Number of days per month (except for February in leap years) 45mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 46 47# This module used to have hard-coded lists of day and month names, as 48# English strings. The classes following emulate a read-only version of 49# that, but supply localized names. Note that the values are computed 50# fresh on each call, in case the user changes locale between calls. 51 52class _localized_month: 53 54 _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)] 55 _months.insert(0, lambda x: "") 56 57 def __init__(self, format): 58 self.format = format 59 60 def __getitem__(self, i): 61 funcs = self._months[i] 62 if isinstance(i, slice): 63 return [f(self.format) for f in funcs] 64 else: 65 return funcs(self.format) 66 67 def __len__(self): 68 return 13 69 70 71class _localized_day: 72 73 # January 1, 2001, was a Monday. 74 _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)] 75 76 def __init__(self, format): 77 self.format = format 78 79 def __getitem__(self, i): 80 funcs = self._days[i] 81 if isinstance(i, slice): 82 return [f(self.format) for f in funcs] 83 else: 84 return funcs(self.format) 85 86 def __len__(self): 87 return 7 88 89 90# Full and abbreviated names of weekdays 91day_name = _localized_day('%A') 92day_abbr = _localized_day('%a') 93 94# Full and abbreviated names of months (1-based arrays!!!) 95month_name = _localized_month('%B') 96month_abbr = _localized_month('%b') 97 98# Constants for weekdays 99(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7) 100 101 102def isleap(year): 103 """Return True for leap years, False for non-leap years.""" 104 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) 105 106 107def leapdays(y1, y2): 108 """Return number of leap years in range [y1, y2). 109 Assume y1 <= y2.""" 110 y1 -= 1 111 y2 -= 1 112 return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400) 113 114 115def weekday(year, month, day): 116 """Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31).""" 117 if not datetime.MINYEAR <= year <= datetime.MAXYEAR: 118 year = 2000 + year % 400 119 return datetime.date(year, month, day).weekday() 120 121 122def monthrange(year, month): 123 """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for 124 year, month.""" 125 if not 1 <= month <= 12: 126 raise IllegalMonthError(month) 127 day1 = weekday(year, month, 1) 128 ndays = mdays[month] + (month == February and isleap(year)) 129 return day1, ndays 130 131 132def _monthlen(year, month): 133 return mdays[month] + (month == February and isleap(year)) 134 135 136def _prevmonth(year, month): 137 if month == 1: 138 return year-1, 12 139 else: 140 return year, month-1 141 142 143def _nextmonth(year, month): 144 if month == 12: 145 return year+1, 1 146 else: 147 return year, month+1 148 149 150class Calendar(object): 151 """ 152 Base calendar class. This class doesn't do any formatting. It simply 153 provides data to subclasses. 154 """ 155 156 def __init__(self, firstweekday=0): 157 self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday 158 159 def getfirstweekday(self): 160 return self._firstweekday % 7 161 162 def setfirstweekday(self, firstweekday): 163 self._firstweekday = firstweekday 164 165 firstweekday = property(getfirstweekday, setfirstweekday) 166 167 def iterweekdays(self): 168 """ 169 Return an iterator for one week of weekday numbers starting with the 170 configured first one. 171 """ 172 for i in range(self.firstweekday, self.firstweekday + 7): 173 yield i%7 174 175 def itermonthdates(self, year, month): 176 """ 177 Return an iterator for one month. The iterator will yield datetime.date 178 values and will always iterate through complete weeks, so it will yield 179 dates outside the specified month. 180 """ 181 for y, m, d in self.itermonthdays3(year, month): 182 yield datetime.date(y, m, d) 183 184 def itermonthdays(self, year, month): 185 """ 186 Like itermonthdates(), but will yield day numbers. For days outside 187 the specified month the day number is 0. 188 """ 189 day1, ndays = monthrange(year, month) 190 days_before = (day1 - self.firstweekday) % 7 191 yield from repeat(0, days_before) 192 yield from range(1, ndays + 1) 193 days_after = (self.firstweekday - day1 - ndays) % 7 194 yield from repeat(0, days_after) 195 196 def itermonthdays2(self, year, month): 197 """ 198 Like itermonthdates(), but will yield (day number, weekday number) 199 tuples. For days outside the specified month the day number is 0. 200 """ 201 for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday): 202 yield d, i % 7 203 204 def itermonthdays3(self, year, month): 205 """ 206 Like itermonthdates(), but will yield (year, month, day) tuples. Can be 207 used for dates outside of datetime.date range. 208 """ 209 day1, ndays = monthrange(year, month) 210 days_before = (day1 - self.firstweekday) % 7 211 days_after = (self.firstweekday - day1 - ndays) % 7 212 y, m = _prevmonth(year, month) 213 end = _monthlen(y, m) + 1 214 for d in range(end-days_before, end): 215 yield y, m, d 216 for d in range(1, ndays + 1): 217 yield year, month, d 218 y, m = _nextmonth(year, month) 219 for d in range(1, days_after + 1): 220 yield y, m, d 221 222 def itermonthdays4(self, year, month): 223 """ 224 Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples. 225 Can be used for dates outside of datetime.date range. 226 """ 227 for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)): 228 yield y, m, d, (self.firstweekday + i) % 7 229 230 def monthdatescalendar(self, year, month): 231 """ 232 Return a matrix (list of lists) representing a month's calendar. 233 Each row represents a week; week entries are datetime.date values. 234 """ 235 dates = list(self.itermonthdates(year, month)) 236 return [ dates[i:i+7] for i in range(0, len(dates), 7) ] 237 238 def monthdays2calendar(self, year, month): 239 """ 240 Return a matrix representing a month's calendar. 241 Each row represents a week; week entries are 242 (day number, weekday number) tuples. Day numbers outside this month 243 are zero. 244 """ 245 days = list(self.itermonthdays2(year, month)) 246 return [ days[i:i+7] for i in range(0, len(days), 7) ] 247 248 def monthdayscalendar(self, year, month): 249 """ 250 Return a matrix representing a month's calendar. 251 Each row represents a week; days outside this month are zero. 252 """ 253 days = list(self.itermonthdays(year, month)) 254 return [ days[i:i+7] for i in range(0, len(days), 7) ] 255 256 def yeardatescalendar(self, year, width=3): 257 """ 258 Return the data for the specified year ready for formatting. The return 259 value is a list of month rows. Each month row contains up to width months. 260 Each month contains between 4 and 6 weeks and each week contains 1-7 261 days. Days are datetime.date objects. 262 """ 263 months = [ 264 self.monthdatescalendar(year, i) 265 for i in range(January, January+12) 266 ] 267 return [months[i:i+width] for i in range(0, len(months), width) ] 268 269 def yeardays2calendar(self, year, width=3): 270 """ 271 Return the data for the specified year ready for formatting (similar to 272 yeardatescalendar()). Entries in the week lists are 273 (day number, weekday number) tuples. Day numbers outside this month are 274 zero. 275 """ 276 months = [ 277 self.monthdays2calendar(year, i) 278 for i in range(January, January+12) 279 ] 280 return [months[i:i+width] for i in range(0, len(months), width) ] 281 282 def yeardayscalendar(self, year, width=3): 283 """ 284 Return the data for the specified year ready for formatting (similar to 285 yeardatescalendar()). Entries in the week lists are day numbers. 286 Day numbers outside this month are zero. 287 """ 288 months = [ 289 self.monthdayscalendar(year, i) 290 for i in range(January, January+12) 291 ] 292 return [months[i:i+width] for i in range(0, len(months), width) ] 293 294 295class TextCalendar(Calendar): 296 """ 297 Subclass of Calendar that outputs a calendar as a simple plain text 298 similar to the UNIX program cal. 299 """ 300 301 def prweek(self, theweek, width): 302 """ 303 Print a single week (no newline). 304 """ 305 print(self.formatweek(theweek, width), end='') 306 307 def formatday(self, day, weekday, width): 308 """ 309 Returns a formatted day. 310 """ 311 if day == 0: 312 s = '' 313 else: 314 s = '%2i' % day # right-align single-digit days 315 return s.center(width) 316 317 def formatweek(self, theweek, width): 318 """ 319 Returns a single week in a string (no newline). 320 """ 321 return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek) 322 323 def formatweekday(self, day, width): 324 """ 325 Returns a formatted week day name. 326 """ 327 if width >= 9: 328 names = day_name 329 else: 330 names = day_abbr 331 return names[day][:width].center(width) 332 333 def formatweekheader(self, width): 334 """ 335 Return a header for a week. 336 """ 337 return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays()) 338 339 def formatmonthname(self, theyear, themonth, width, withyear=True): 340 """ 341 Return a formatted month name. 342 """ 343 s = month_name[themonth] 344 if withyear: 345 s = "%s %r" % (s, theyear) 346 return s.center(width) 347 348 def prmonth(self, theyear, themonth, w=0, l=0): 349 """ 350 Print a month's calendar. 351 """ 352 print(self.formatmonth(theyear, themonth, w, l), end='') 353 354 def formatmonth(self, theyear, themonth, w=0, l=0): 355 """ 356 Return a month's calendar string (multi-line). 357 """ 358 w = max(2, w) 359 l = max(1, l) 360 s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1) 361 s = s.rstrip() 362 s += '\n' * l 363 s += self.formatweekheader(w).rstrip() 364 s += '\n' * l 365 for week in self.monthdays2calendar(theyear, themonth): 366 s += self.formatweek(week, w).rstrip() 367 s += '\n' * l 368 return s 369 370 def formatyear(self, theyear, w=2, l=1, c=6, m=3): 371 """ 372 Returns a year's calendar as a multi-line string. 373 """ 374 w = max(2, w) 375 l = max(1, l) 376 c = max(2, c) 377 colwidth = (w + 1) * 7 - 1 378 v = [] 379 a = v.append 380 a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip()) 381 a('\n'*l) 382 header = self.formatweekheader(w) 383 for (i, row) in enumerate(self.yeardays2calendar(theyear, m)): 384 # months in this row 385 months = range(m*i+1, min(m*(i+1)+1, 13)) 386 a('\n'*l) 387 names = (self.formatmonthname(theyear, k, colwidth, False) 388 for k in months) 389 a(formatstring(names, colwidth, c).rstrip()) 390 a('\n'*l) 391 headers = (header for k in months) 392 a(formatstring(headers, colwidth, c).rstrip()) 393 a('\n'*l) 394 # max number of weeks for this row 395 height = max(len(cal) for cal in row) 396 for j in range(height): 397 weeks = [] 398 for cal in row: 399 if j >= len(cal): 400 weeks.append('') 401 else: 402 weeks.append(self.formatweek(cal[j], w)) 403 a(formatstring(weeks, colwidth, c).rstrip()) 404 a('\n' * l) 405 return ''.join(v) 406 407 def pryear(self, theyear, w=0, l=0, c=6, m=3): 408 """Print a year's calendar.""" 409 print(self.formatyear(theyear, w, l, c, m), end='') 410 411 412class HTMLCalendar(Calendar): 413 """ 414 This calendar returns complete HTML pages. 415 """ 416 417 # CSS classes for the day <td>s 418 cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] 419 420 # CSS classes for the day <th>s 421 cssclasses_weekday_head = cssclasses 422 423 # CSS class for the days before and after current month 424 cssclass_noday = "noday" 425 426 # CSS class for the month's head 427 cssclass_month_head = "month" 428 429 # CSS class for the month 430 cssclass_month = "month" 431 432 # CSS class for the year's table head 433 cssclass_year_head = "year" 434 435 # CSS class for the whole year table 436 cssclass_year = "year" 437 438 def formatday(self, day, weekday): 439 """ 440 Return a day as a table cell. 441 """ 442 if day == 0: 443 # day outside month 444 return '<td class="%s"> </td>' % self.cssclass_noday 445 else: 446 return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day) 447 448 def formatweek(self, theweek): 449 """ 450 Return a complete week as a table row. 451 """ 452 s = ''.join(self.formatday(d, wd) for (d, wd) in theweek) 453 return '<tr>%s</tr>' % s 454 455 def formatweekday(self, day): 456 """ 457 Return a weekday name as a table header. 458 """ 459 return '<th class="%s">%s</th>' % ( 460 self.cssclasses_weekday_head[day], day_abbr[day]) 461 462 def formatweekheader(self): 463 """ 464 Return a header for a week as a table row. 465 """ 466 s = ''.join(self.formatweekday(i) for i in self.iterweekdays()) 467 return '<tr>%s</tr>' % s 468 469 def formatmonthname(self, theyear, themonth, withyear=True): 470 """ 471 Return a month name as a table row. 472 """ 473 if withyear: 474 s = '%s %s' % (month_name[themonth], theyear) 475 else: 476 s = '%s' % month_name[themonth] 477 return '<tr><th colspan="7" class="%s">%s</th></tr>' % ( 478 self.cssclass_month_head, s) 479 480 def formatmonth(self, theyear, themonth, withyear=True): 481 """ 482 Return a formatted month as a table. 483 """ 484 v = [] 485 a = v.append 486 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % ( 487 self.cssclass_month)) 488 a('\n') 489 a(self.formatmonthname(theyear, themonth, withyear=withyear)) 490 a('\n') 491 a(self.formatweekheader()) 492 a('\n') 493 for week in self.monthdays2calendar(theyear, themonth): 494 a(self.formatweek(week)) 495 a('\n') 496 a('</table>') 497 a('\n') 498 return ''.join(v) 499 500 def formatyear(self, theyear, width=3): 501 """ 502 Return a formatted year as a table of tables. 503 """ 504 v = [] 505 a = v.append 506 width = max(width, 1) 507 a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % 508 self.cssclass_year) 509 a('\n') 510 a('<tr><th colspan="%d" class="%s">%s</th></tr>' % ( 511 width, self.cssclass_year_head, theyear)) 512 for i in range(January, January+12, width): 513 # months in this row 514 months = range(i, min(i+width, 13)) 515 a('<tr>') 516 for m in months: 517 a('<td>') 518 a(self.formatmonth(theyear, m, withyear=False)) 519 a('</td>') 520 a('</tr>') 521 a('</table>') 522 return ''.join(v) 523 524 def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None): 525 """ 526 Return a formatted year as a complete HTML page. 527 """ 528 if encoding is None: 529 encoding = sys.getdefaultencoding() 530 v = [] 531 a = v.append 532 a('<?xml version="1.0" encoding="%s"?>\n' % encoding) 533 a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n') 534 a('<html>\n') 535 a('<head>\n') 536 a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding) 537 if css is not None: 538 a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css) 539 a('<title>Calendar for %d</title>\n' % theyear) 540 a('</head>\n') 541 a('<body>\n') 542 a(self.formatyear(theyear, width)) 543 a('</body>\n') 544 a('</html>\n') 545 return ''.join(v).encode(encoding, "xmlcharrefreplace") 546 547 548class different_locale: 549 def __init__(self, locale): 550 self.locale = locale 551 self.oldlocale = None 552 553 def __enter__(self): 554 self.oldlocale = _locale.setlocale(_locale.LC_TIME, None) 555 _locale.setlocale(_locale.LC_TIME, self.locale) 556 557 def __exit__(self, *args): 558 if self.oldlocale is None: 559 return 560 _locale.setlocale(_locale.LC_TIME, self.oldlocale) 561 562 563def _get_default_locale(): 564 locale = _locale.setlocale(_locale.LC_TIME, None) 565 if locale == "C": 566 with different_locale(""): 567 # The LC_TIME locale does not seem to be configured: 568 # get the user preferred locale. 569 locale = _locale.setlocale(_locale.LC_TIME, None) 570 return locale 571 572 573class LocaleTextCalendar(TextCalendar): 574 """ 575 This class can be passed a locale name in the constructor and will return 576 month and weekday names in the specified locale. 577 """ 578 579 def __init__(self, firstweekday=0, locale=None): 580 TextCalendar.__init__(self, firstweekday) 581 if locale is None: 582 locale = _get_default_locale() 583 self.locale = locale 584 585 def formatweekday(self, day, width): 586 with different_locale(self.locale): 587 return super().formatweekday(day, width) 588 589 def formatmonthname(self, theyear, themonth, width, withyear=True): 590 with different_locale(self.locale): 591 return super().formatmonthname(theyear, themonth, width, withyear) 592 593 594class LocaleHTMLCalendar(HTMLCalendar): 595 """ 596 This class can be passed a locale name in the constructor and will return 597 month and weekday names in the specified locale. 598 """ 599 def __init__(self, firstweekday=0, locale=None): 600 HTMLCalendar.__init__(self, firstweekday) 601 if locale is None: 602 locale = _get_default_locale() 603 self.locale = locale 604 605 def formatweekday(self, day): 606 with different_locale(self.locale): 607 return super().formatweekday(day) 608 609 def formatmonthname(self, theyear, themonth, withyear=True): 610 with different_locale(self.locale): 611 return super().formatmonthname(theyear, themonth, withyear) 612 613# Support for old module level interface 614c = TextCalendar() 615 616firstweekday = c.getfirstweekday 617 618def setfirstweekday(firstweekday): 619 if not MONDAY <= firstweekday <= SUNDAY: 620 raise IllegalWeekdayError(firstweekday) 621 c.firstweekday = firstweekday 622 623monthcalendar = c.monthdayscalendar 624prweek = c.prweek 625week = c.formatweek 626weekheader = c.formatweekheader 627prmonth = c.prmonth 628month = c.formatmonth 629calendar = c.formatyear 630prcal = c.pryear 631 632 633# Spacing of month columns for multi-column year calendar 634_colwidth = 7*3 - 1 # Amount printed by prweek() 635_spacing = 6 # Number of spaces between columns 636 637 638def format(cols, colwidth=_colwidth, spacing=_spacing): 639 """Prints multi-column formatting for year calendars""" 640 print(formatstring(cols, colwidth, spacing)) 641 642 643def formatstring(cols, colwidth=_colwidth, spacing=_spacing): 644 """Returns a string formatted from n strings, centered within n columns.""" 645 spacing *= ' ' 646 return spacing.join(c.center(colwidth) for c in cols) 647 648 649EPOCH = 1970 650_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal() 651 652 653def timegm(tuple): 654 """Unrelated but handy function to calculate Unix timestamp from GMT.""" 655 year, month, day, hour, minute, second = tuple[:6] 656 days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1 657 hours = days*24 + hour 658 minutes = hours*60 + minute 659 seconds = minutes*60 + second 660 return seconds 661 662 663def main(args): 664 import argparse 665 parser = argparse.ArgumentParser() 666 textgroup = parser.add_argument_group('text only arguments') 667 htmlgroup = parser.add_argument_group('html only arguments') 668 textgroup.add_argument( 669 "-w", "--width", 670 type=int, default=2, 671 help="width of date column (default 2)" 672 ) 673 textgroup.add_argument( 674 "-l", "--lines", 675 type=int, default=1, 676 help="number of lines for each week (default 1)" 677 ) 678 textgroup.add_argument( 679 "-s", "--spacing", 680 type=int, default=6, 681 help="spacing between months (default 6)" 682 ) 683 textgroup.add_argument( 684 "-m", "--months", 685 type=int, default=3, 686 help="months per row (default 3)" 687 ) 688 htmlgroup.add_argument( 689 "-c", "--css", 690 default="calendar.css", 691 help="CSS to use for page" 692 ) 693 parser.add_argument( 694 "-L", "--locale", 695 default=None, 696 help="locale to be used from month and weekday names" 697 ) 698 parser.add_argument( 699 "-e", "--encoding", 700 default=None, 701 help="encoding to use for output" 702 ) 703 parser.add_argument( 704 "-t", "--type", 705 default="text", 706 choices=("text", "html"), 707 help="output type (text or html)" 708 ) 709 parser.add_argument( 710 "year", 711 nargs='?', type=int, 712 help="year number (1-9999)" 713 ) 714 parser.add_argument( 715 "month", 716 nargs='?', type=int, 717 help="month number (1-12, text only)" 718 ) 719 720 options = parser.parse_args(args[1:]) 721 722 if options.locale and not options.encoding: 723 parser.error("if --locale is specified --encoding is required") 724 sys.exit(1) 725 726 locale = options.locale, options.encoding 727 728 if options.type == "html": 729 if options.locale: 730 cal = LocaleHTMLCalendar(locale=locale) 731 else: 732 cal = HTMLCalendar() 733 encoding = options.encoding 734 if encoding is None: 735 encoding = sys.getdefaultencoding() 736 optdict = dict(encoding=encoding, css=options.css) 737 write = sys.stdout.buffer.write 738 if options.year is None: 739 write(cal.formatyearpage(datetime.date.today().year, **optdict)) 740 elif options.month is None: 741 write(cal.formatyearpage(options.year, **optdict)) 742 else: 743 parser.error("incorrect number of arguments") 744 sys.exit(1) 745 else: 746 if options.locale: 747 cal = LocaleTextCalendar(locale=locale) 748 else: 749 cal = TextCalendar() 750 optdict = dict(w=options.width, l=options.lines) 751 if options.month is None: 752 optdict["c"] = options.spacing 753 optdict["m"] = options.months 754 if options.year is None: 755 result = cal.formatyear(datetime.date.today().year, **optdict) 756 elif options.month is None: 757 result = cal.formatyear(options.year, **optdict) 758 else: 759 result = cal.formatmonth(options.year, options.month, **optdict) 760 write = sys.stdout.write 761 if options.encoding: 762 result = result.encode(options.encoding) 763 write = sys.stdout.buffer.write 764 write(result) 765 766 767if __name__ == "__main__": 768 main(sys.argv) 769