En Python, un contexto (context) es una clase que implementa los métodos ‘__enter__’ y ‘__exit__’ los cuales son llamados si la clase en llamada con la palabra reservada ‘with’. Por ejemplo, los descriptores de archivo (file handle) en Python se pueden llamar con un contexto, ahorrando llamar ‘finally’ para cerrar archivos, sin importar si hay un error.
Les traigo de vuelta el programa de ‘DolarToday’ el cual escribe datos en un archivo binario, pero ahora utilizando contextos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | #!/usr/bin/env python3 # Simple program to save the 'Dolartoday' extra official dollar rates from CSV to a custom format # Revisited to use 'contexts' to avoid using finally on exception handling # @author Jose Vicente Nunez, josevnz@kodegeek.com import os, sys, datetime, re, gzip, struct from optparse import OptionParser, OptionValueError from datetime import datetime class DollarToday: def __init__(self, ddate, value): if isinstance(ddate, datetime): self.__date = ddate else: self.__date = datetime.strptime(ddate, "%m-%d-%Y") self.__value = float(value) assert self.__value > 0.0, "{} for date {} is invalid!".format(value, self.__date) @property def date(self): return self.__date @date.setter def date(self, date): assert isinstance(date, datetime), "Invalid date {}".format(date) self.__date = date @property def value(self): return self.__value def __str__(self): return "DollarToday[date={}, value={}]".format(self.__date, self.__value) def __hash__(self): return str(id(self)) class DollarCollection(dict): # https://en.wikipedia.org/wiki/List_of_file_signatures __FILE_MAGIC = b"DLR\x00" __FILE_VERSION = b"\x00\x01" __dollarStruct = struct.Struct("<id ") def values(self): for dateId in sorted(self.keys()): yield self[dateId] def items(self): for dateId in self.keys(): yield (dateId, self[dateId]) def __iter__(self): for dateId in sorted(super().keys()): yield dateId def save(self, file): try: with gzip.open(file, "wb") as fh: fh.write(self.__FILE_MAGIC) fh.write(self.__FILE_VERSION) for dollar in self.values(): data = bytearray() data.extend( self.__dollarStruct.pack( dollar.date.toordinal(), dollar.value ) ) fh.write(data) except (Exception) as err: raise def readBinary(self, file, verbose=False): try: with gzip.open(file, "rb") as fh: magic = fh.read(len(self.__FILE_MAGIC)) if magic != self.__FILE_MAGIC: raise "File doesn't look like a KodeGeek.com binary file!" version = fh.read(len(self.__FILE_VERSION)) if version > self.__FILE_VERSION: raise "Unsupported file version: {}, expected {}".format(version, self.__FILE_VERSION) self.clear() while True: data = fh.read(self.__dollarStruct.size) if len(data) == 0: break numbers = self.__dollarStruct.unpack(data) #if verbose: # print("{}".format(",".join([str(x) for x in numbers]))) dolar = DollarToday( datetime.fromordinal(numbers[0]), numbers[1] ) self[dolar.date] = dolar except (Exception) as err: raise def readCsv(self, file): tempMap = {} try: with open(file, "r", encoding="UTF-8") as fh: for line in fh.readlines(): # 6-23-2010 9.92 (date, value) = line.strip().split("\t") if re.match("Fecha", date): continue try: dolVsBol = DollarToday(date, value) tempMap[dolVsBol.date] = dolVsBol except (Exception) as err: print(err) self.clear() self.update(tempMap) except (Exception) as err: raise def __str__(self): return "Records={},\n{}".format(len(self), ",\n".join([str(date) for date in self.values()])) def main(options): verbose = options.verbose dc = DollarCollection() if options.report == None: dc.readCsv(options.read) dc.save(options.write) else: dc.readBinary(options.report, verbose) if verbose: print("{}".format(dc)) if __name__ == "__main__": usagetext = """ %prog --read csv.file --write binary.file Or: %prog --report binary.file """ op = OptionParser(usage=usagetext) op.add_option( "-r", "--read", action="store", dest="read", help="Full path to CSV file with date,value pairs per line") op.add_option( "-w", "--write", action="store", dest="write", help="Full path to destination file in binary format") op.add_option( "-p", "--report", action="store", dest="report", help="Read the contents of the binary storage and generate a report. Incompatible with --read and --write") op.add_option( "-v", "--verbose", action="store_true", default=False, dest="verbose", help="Enable verbose mode") (options, values) = op.parse_args() main(options) </id> |