{"id":3760,"date":"2016-03-09T06:00:11","date_gmt":"2016-03-09T11:00:11","guid":{"rendered":"http:\/\/kodegeek.com\/blog\/?p=3760"},"modified":"2016-03-06T18:00:04","modified_gmt":"2016-03-06T23:00:04","slug":"contexts-in-python","status":"publish","type":"post","link":"http:\/\/kodegeek.com\/blog\/2016\/03\/09\/contexts-in-python\/","title":{"rendered":"&#8216;Contexts&#8217; en Python"},"content":{"rendered":"<p>En Python, un contexto (context) es una clase que implementa los m\u00e9todos &#8216;__enter__&#8217; y &#8216;__exit__&#8217; los cuales son llamados si la clase en llamada con la palabra reservada &#8216;with&#8217;. Por ejemplo, los descriptores de archivo (file handle) en Python se pueden llamar con un contexto, ahorrando llamar &#8216;finally&#8217; para cerrar archivos, sin importar si hay un error.<\/p>\n<p>Les traigo de vuelta el programa de &#8216;DolarToday&#8217; el cual escribe datos en un archivo binario, pero ahora utilizando contextos:<\/p>\n<pre lang=\"python\">\r\n#!\/usr\/bin\/env python3\r\n# Simple program to save the 'Dolartoday' extra official dollar rates from CSV to a custom format\r\n# Revisited to use 'contexts' to avoid using finally on exception handling\r\n# @author Jose Vicente Nunez, josevnz@kodegeek.com\r\nimport os, sys, datetime, re, gzip, struct\r\nfrom optparse import OptionParser, OptionValueError\r\nfrom datetime import datetime\r\n\r\nclass DollarToday:\r\n    \r\n    def __init__(self, ddate, value):\r\n        if isinstance(ddate, datetime):\r\n            self.__date = ddate\r\n        else:\r\n            self.__date = datetime.strptime(ddate, \"%m-%d-%Y\")\r\n        self.__value = float(value)\r\n        assert self.__value > 0.0, \"{} for date {} is invalid!\".format(value, self.__date)\r\n        \r\n    @property\r\n    def date(self):\r\n        return self.__date\r\n    \r\n    @date.setter\r\n    def date(self, date):\r\n        assert isinstance(date, datetime), \"Invalid date {}\".format(date)\r\n        self.__date = date\r\n    \r\n    @property\r\n    def value(self):\r\n        return self.__value\r\n    \r\n    def __str__(self):\r\n        return \"DollarToday[date={}, value={}]\".format(self.__date, self.__value)\r\n    \r\n    def __hash__(self):\r\n        return str(id(self))\r\n            \r\nclass DollarCollection(dict):\r\n    \r\n    # https:\/\/en.wikipedia.org\/wiki\/List_of_file_signatures\r\n    __FILE_MAGIC = b\"DLR\\x00\"\r\n    __FILE_VERSION = b\"\\x00\\x01\"\r\n    __dollarStruct = struct.Struct(\"<id \")\r\n    \r\n    def values(self):\r\n        for dateId in sorted(self.keys()):\r\n            yield self[dateId]\r\n    \r\n    def items(self):\r\n        for dateId in self.keys():\r\n            yield (dateId, self[dateId])\r\n    \r\n    def __iter__(self):\r\n        for dateId in sorted(super().keys()):\r\n            yield dateId\r\n\r\n    def save(self, file):\r\n        try:\r\n            with gzip.open(file, \"wb\") as fh:\r\n                fh.write(self.__FILE_MAGIC)\r\n                fh.write(self.__FILE_VERSION)\r\n                for dollar in self.values():\r\n                    data = bytearray()\r\n                    data.extend(\r\n                           self.__dollarStruct.pack(\r\n                                dollar.date.toordinal(),\r\n                                dollar.value\r\n                           )\r\n                                )\r\n                    fh.write(data)\r\n        except (Exception) as err:\r\n            raise\r\n\r\n    def readBinary(self, file, verbose=False):\r\n        try:\r\n            with gzip.open(file, \"rb\") as fh:\r\n                magic = fh.read(len(self.__FILE_MAGIC))\r\n                if magic != self.__FILE_MAGIC:\r\n                    raise \"File doesn't look like a KodeGeek.com binary file!\"\r\n                version = fh.read(len(self.__FILE_VERSION))\r\n                if version > self.__FILE_VERSION:\r\n                    raise \"Unsupported file version: {}, expected {}\".format(version, self.__FILE_VERSION)\r\n                self.clear()\r\n                while True:\r\n                    data = fh.read(self.__dollarStruct.size)\r\n                    if len(data) == 0:\r\n                        break\r\n                    numbers = self.__dollarStruct.unpack(data)\r\n                    #if verbose:\r\n                    #    print(\"{}\".format(\",\".join([str(x) for x in numbers])))\r\n                    dolar = DollarToday(\r\n                                    datetime.fromordinal(numbers[0]),\r\n                                    numbers[1]\r\n                                    )\r\n                    self[dolar.date] = dolar\r\n        except (Exception) as err:\r\n            raise\r\n                \r\n    def readCsv(self, file):\r\n        tempMap = {}\r\n        try:\r\n            with open(file, \"r\", encoding=\"UTF-8\") as fh:\r\n                for line in fh.readlines():\r\n                    # 6-23-2010       9.92\r\n                    (date, value) = line.strip().split(\"\\t\")\r\n                    if re.match(\"Fecha\", date):\r\n                        continue\r\n                    try:\r\n                        dolVsBol = DollarToday(date, value)\r\n                        tempMap[dolVsBol.date] = dolVsBol\r\n                    except (Exception) as err:\r\n                        print(err)\r\n            self.clear()\r\n            self.update(tempMap)\r\n        except (Exception) as err:\r\n            raise\r\n\r\n    def __str__(self):\r\n        return \"Records={},\\n{}\".format(len(self), \",\\n\".join([str(date) for date in self.values()]))\r\n\r\ndef main(options):\r\n\r\n    verbose = options.verbose\r\n    dc = DollarCollection()\r\n    if options.report == None:\r\n        dc.readCsv(options.read)\r\n        dc.save(options.write)\r\n    else:\r\n        dc.readBinary(options.report, verbose)\r\n    if verbose:\r\n        print(\"{}\".format(dc))\r\n\r\nif __name__ == \"__main__\":\r\n    \r\n    usagetext = \"\"\"\r\n%prog --read csv.file --write binary.file\r\n\r\nOr:\r\n\r\n%prog --report binary.file\r\n\r\n\"\"\"\r\n\r\n    op = OptionParser(usage=usagetext)\r\n    op.add_option(\r\n                  \"-r\", \"--read\",\r\n                  action=\"store\", \r\n                  dest=\"read\", \r\n                  help=\"Full path to CSV file with date,value pairs per line\")\r\n    op.add_option(\r\n                  \"-w\", \"--write\", \r\n                  action=\"store\", \r\n                  dest=\"write\", \r\n                  help=\"Full path to destination file in binary format\")\r\n    op.add_option(\r\n                  \"-p\", \"--report\", \r\n                  action=\"store\", \r\n                  dest=\"report\",\r\n                  help=\"Read the contents of the binary storage and generate a report. Incompatible with --read and --write\")\r\n    op.add_option(\r\n                  \"-v\", \"--verbose\", \r\n                  action=\"store_true\", \r\n                  default=False, \r\n                  dest=\"verbose\", \r\n                  help=\"Enable verbose mode\")\r\n    \r\n    (options, values) = op.parse_args()\r\n\r\n    main(options)\r\n<\/id><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>En Python, un contexto (context) es una clase que implementa los m\u00e9todos &#8216;__enter__&#8217; y &#8216;__exit__&#8217; los cuales son llamados si la clase en llamada con la palabra reservada &#8216;with&#8217;. Por ejemplo, los descriptores de archivo (file handle) en Python se pueden llamar con un contexto, ahorrando llamar &#8216;finally&#8217; para cerrar archivos, sin importar si hay <a class=\"read-more\" href=\"http:\/\/kodegeek.com\/blog\/2016\/03\/09\/contexts-in-python\/\">[&hellip;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[438,239],"tags":[799,791,800,765],"_links":{"self":[{"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/posts\/3760"}],"collection":[{"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/comments?post=3760"}],"version-history":[{"count":2,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/posts\/3760\/revisions"}],"predecessor-version":[{"id":3762,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/posts\/3760\/revisions\/3762"}],"wp:attachment":[{"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/media?parent=3760"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/categories?post=3760"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/kodegeek.com\/blog\/wp-json\/wp\/v2\/tags?post=3760"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}