Section 1.6 Files of Practical Python Programming follows a similar to the previous two sections. It does have one exercise that calls for a complete program but it is such a short one that I included all the examples and exercises in the single file - files.rkt
The Python examples and exercises are designed to be run from the Python console started from a specified directory. I cannot be certain from where files.rkt will be run so in the script I needed to make sure the correct filepaths, relative to the directory from which the script was started, would be used. This is a perenial banana skin for me in most languages. I found a possible solution by referring to Stack Overflow. It wasn't elegant but it did work. Thankfully I checked on the Racket Mailing List and was kindly pointed to the define-runtime-path function which, happily for me, takes care of the problem for you.
You can achieve the same in Python but, as far as I know, not quite so easily.
(define-runtime-path f "my-file.txt")
f = os.path.join(os.path.dirname(__file__), 'my-file.txt')
The examples start with opening text files and reading and writing their contents into and out of strings. Translating them was relatively straight forward. The only thing that I note is that in Racket you open a file but then refer to it as a port. (Again this may be a lack of understanding on my part). This might be a little confusing to a newcomer but, having been introduced to ports in Rebol, it didn't have that effect on me.
(define f (open-input-file foo.txt #:mode 'text))
This is maybe a little academic as Racket's file->string function reads the contents of a file into a string, automatically opening and closing the file. (The same is true of Racket's write-to-file function for writing a string to a file.)
The Python examples for processing data use Python's context manager feature which automatically closes a file once it has been processed. Racket's file->string and file->lines do the same more concisely as there in no need to explicitly open the file as there is with a Python context manager.
The Python next function is used in Exercise 1.26 File Preliminaries. As the example only calls next() once, I used Racket's first and rest on a list of lines read from the file to simulate it.
I also came up with a solution that was closer to the python example:
(define f (open-input-file Data/portfolio.csv #:mode 'text)))
(define headers (read-line f))) ;"remove" the headers from the port
(define rows (port->liines f)) ; read the remainder of the file
(for-each (lambda (r) (displayln r)) rows))
The one example that called for separate program was to calculate the cost of a share portfolio where the number of shares and their individual costs was stored in a csv file. Here is my solution, a pretty straight forward recursive function:
(define csv-rows (file->lines Data/portfolio.csv #:mode 'text)))
(define (pcost rows)
[(empty? rows) 0]
[else (define row (string-split (first rows) ","))
(+ (* (string->number (second row))
(string->number (third row)))
(pcost (rest rows)))]))
(display "Total cost "))
(displayln (pcost (rest csv-rows))))
The final exercise was to open a gzipped version of a csv file. Both Python and Racket required gzip libraries to be imported/required from their standard libraries. Though neither requires an additional library to be installed. The only item of note is that the Racket gunzip function requires an output string to be passed as a parameter into which it decompressed the data. At first glance, Racket's output-strings seem similar to Python StringIO objects. I fully expect to have to find out more as I progress with translating Practical Python Programming. Here is my translation of the Python example:
(define gzip (open-input-file "Data/portfolio.csv.gz"))
(define csv (open-output-string))
(gunzip-through-ports gzip csv)
(displayln (get-output-string csv))
So that wraps up the Introduction to Files. Next up is 1.7 Functions which I fully expect to require some study on my part before I'll be able to come up with a translation.