Learn Python the Hard Way Exercise 17 Extra Question(S) - python

I'm doing Zed Shaw's fantastic Learn Python The Hard Way, but an extra question has me stumped: Line 9--10 could be written in one line, how? I've tried some different thoughts, but to no avail. I could move on, but what would the fun in that be?
from sys import argv
from os.path import exists
script, from_file, to_file = argv
print "Copying from %s to %s" % (from_file, to_file)
# we could do these two on one line too, how?
input = open(from_file)
indata = input.read()
print "The input file is %d bytes long" % len(indata)
print "Does the output file exist? %r" % exists(to_file)
print "Ready, hit RETURN to continue, CTRL-C to abort."
raw_input()
output = open(to_file, 'w')
output.write(indata)
print "Alright, all done."
Zed also writes that he could do the whole script in one line. I'm not exactly sure what he means by that.
Feel free to help me however you want: by giving the answer or merely hinting---and perhaps including a collapsed or hidden answer to the question.

indata = open(from_file).read()

shutil is the way to do one-liner file copies in Python:
shutil.copy(sys.argv[1], sys.argv[2])
Putting the import shutil, sys on the same line as this one (with a semicolon in-between, of course) would however be stylistically goofy;-).

Well you can just do "algebraic substitution," right? ...assuming you don't care about the "UI"...
open(to_file, 'w').write(open(from_file).read())

from sys import argv
open(argv[2], 'w').write(open(argv[1]).read())

I agree with the algebraic substitution mentioned by #dash-tom-bang.
My functioning Exercise 17 extra credit has 5 lines. The operation is being conducted on one line.
open(to_file, 'w').write(open(from_file).read())
followed by a simple 'print' for verification feedback
print "File %s copied to %s" % (from_file, to_file)
I should have a 6th line that replaces the original ''output.close'' but I am confused about how to do this without the ''output'' variable? Ahh, since I now have no output variable there is nothing to close.
btw- It is a little spooky for me to see the same exact line typed here that I have worked out and entered myself in gedit. Great stuff, I am really enjoying the mental challenge and community support.
Edit:answered my own question

try the following code:
import shutil, sys; shutil.copy(sys.argv[0], sys.argv[2])

output = open(to_file, 'w')
output.write(indata)
can be written as
open(to_file, 'w').write(indata)

Had some fun with this one. In answering this I was looking for a solution that preserved the functionality, used the file commands the exercise was about and did not use ";" to combine lines. Closest I could come is
open(input("Out?: "), 'w').write(open(input("In?: ")).read())
Not quite the same functionality as it prompts for input rather than taking command line. But a single line that gets the job done while using the file commands in the exercise and avoiding concatenating lines using semi colon.

He answers this below in the section "Common Student Questions":
No way you can make this one line!
That ; depends ; on ; how ; you ; define ; one ; line ; of ; code.

Hey Kiwi (and whomever else finds this!),
I'm on the same exercise and I believe I've cracked it.
There are two possible readings of Shaw's "I could make this one line long" tease.
He could make the Python script one line long, upon importing all the necessary commands from the modules, e.g from sys import argv, etc.
He could copy the contents of one file to another in one line using the command line.
I personally think he means the latter, but I will explain both solutions for the sake of learning!
The first (Long) solution:
You must acknowledge that you require the importx from y lines in the Python file, otherwise argv and exist won't work because they will only have been implicitly referenced, i.e. you haven't made it clear to Python that you want to use these functions.
The next thing to do is delete all irrelevant code, with irrelevant being code that is written for the benefit of the user, i.e. print, raw_input(), len(), etc.
If you do this, you will be left with:
from sys import argv
from os.path import exists
script, from_file, to_file = argv
indata = open(from_file).read()
out_file = open(to_file, 'w')
out_file.write(indata)
out_file.close()
in_file.close()
To make this even shorter, you can begin nesting the variables and function in one another. This is the same principle as in maths when you could define a function and then substitute the variable representing that function into another function.
For example:
y = x + 3
z = y, which is essentially z = (x + 3)
If you work this through, you can simplify the code down to:
from sys import argv
from os.path import exists
script, from_file, to_file = argv
(open(to_file, 'w').write(open(from_file).read()))
You can then use lots of ; to link up all the lines of code and vio-la you're done.
Note: You don't need to close the files, as you did in the original, as Python will automatically close them upon executing the script.
The second (Short) solution:
If you look at his 'What You Should See' section, he uses cat in the terminal. This is short for concatenation, which is a means of connecting strings together. If you combine it with > you can overwrite the contents of one file with another in one line:
cat from_file.txt > to_file.txt
That's it. One line that will take the contents of one file and put it into another.
Of course, both solutions aren't perfect, as the first isn't truly one line and the second doesn't even use Python!
Feedback appreciated, I only started doing this two days ago...

Cutting away everything you don't need, all the 'features you don't need' as Zed puts it, and you get one line. It's even less than 80 characters in length, can't get much more 'pythonic' than that!
from sys import argv; s, f, t = argv; (open(t, 'w').write(open(f).read()))

All he is saying is that you can use a semicolon to put two lines onto one line and have it run
in_file = open(from_file); indata = in_file.read()
You could do that with the whole piece of code if you wanted to

I'm also doing the same book online. I tried this, and it worked:
open(to_file, 'w').write(open(from_file).read())
In other words, I opened the file I was copying to in the write mode and then inserted the contents of the file I was copying from, after opening and reading it, into the write function. I checked my files and it worked. Yay!

The below line worked for me:
open(to_file, 'w').write(open(from_file).read())

There was a hint within the Common Student Questions section:
"No way you can make this one line!
That ; depends ; on ; how ; you ; defi ne ; one ; line ; of ; code."
Note: This is clearly not best practice and difficult to read.
from sys import argv
script, from_file, to_file = argv
in_file = open(from_file); indata = in_file.read(); out_file = open(to_file, 'w'); out_file.write(indata); out_file.close(); in_file.close()

This the most reduced code that preserves the ui maybe there is other better solutions.
From 21 lines down to 8 lines of code.
Before:
from sys import argv
from os.path import exists
script, from_file, to_file = argv
print("Copying from %s to %s" % (from_file, to_file))
# we could do these two on one line too, how?
input = open(from_file)
indata = input.read()
print("The input file is %d bytes long" % len(indata))
print("Does the output file exist? %r" % exists(to_file))
print("Ready, hit RETURN to continue, CTRL-C to abort.")
#raw_input()
output = open(to_file, 'w')
output.write(indata)
print("Alright, all done.")
To:
from sys import argv
from os.path import exists
script, from_file, to_file = argv
output = open(to_file, 'w').write(open(from_file).read())
print("""Copying from %s to %s\nThe input file is %d bytes long\nDoes the output file exist? %r\nReady, hit RETURN to continue, CTRL-C to abort.\nAlright, all done.""" % (from_file, to_file, len(open(from_file).read()), exists(to_file)))

Related

Python String Query

I recently made a Twitter-bot that takes a specified .txt file and tweets it out, line by line. A lot of the features I built into the program to troubleshoot some formatting issues actually allows the program to work with pretty much any text file.
I would like build in a feature where I can "import" a .txt file to use.
I put that in quotes because the program runs in the command line at them moment.
I figured there are two ways I can tackle this problem but need some guidance on each:
A) I begin the program with a prompt asking which file the user want to use. This is stored as a string (lets say variable string) and the code looks like this-
file = open(string,'r')
There are two main issues with. The first is I'm unsure how to keep the program from crashing if the program specified is misspelled or does not exist. The second is that it won't mesh with future development (eventually I'd like to build app functionality around this program)
B) Somehow specify the desired file somehow in the command line. While the program will still occasionally crash, it isn't as inconvenient to the user. Also, this would lend itself to future development, as it'll be easier to pass a value in through the command line than an internal prompt.
Any ideas?
For the first part of the question, exception handling is the way to go . Though for the second part you can also use a module called argparse.
import argparse
# creating command line argument with the name file_name
parser = argparse.ArgumentParser()
parser.add_argument("file_name", help="Enter file name")
args = parser.parse_args()
# reading the file
with open(args.file_name,'r') as f:
file = f.read()
You can read more about the argparse module on its documentation page.
Regarding A), you may want to investigate
try:
with open(fname) as f:
blah = f.read()
except Exception as ex:
# handle error
and for B) you can, e.g.
import sys
fname = sys.argv[1]
You could also combine the both to make sure that the user has passed an argument:
#!/usr/bin/env python
# encoding: utf-8
import sys
def tweet_me(text):
# your bot goes here
print text
if __name__ == '__main__':
try:
fname = sys.argv[1]
with open(fname) as f:
blah = f.read()
tweet_me(blah)
except Exception as ex:
print ex
print "Please call this as %s <name-of-textfile>" % sys.argv[0]
Just in case someone wonders about the # encoding: utf-8 line. This allows the source code to contain utf-8 characters. Otherwise only ASCII is allowed, which would be ok for this script. So the line is not necessary. I was, however, testing the script on itself (python x.py x.py) and, as a little test, added a utf-8 comment (# รค). In real life, you will have to care a lot more for character encoding of your input...
Beware, however, that just catching any Exception that may arise from the whole program is not considered good coding style. While Python encourages to assume the best and try it, it might be wise to catch expectable errors right where they happen. For example , accessing a file which does not exist will raise an IOError. You may end up with something like:
except IndexError as ex:
print "Syntax: %s <text-file>" % sys.argv[0]
except IOError as ex:
print "Please provide an existing (and accessible) text-file."
except Exception as ex:
print "uncaught Exception:", type(ex)

File is created but cannot be written in Python

I am trying to write some results I get from a function for a range but I don't understand why the file is empty. The function is working fine because I can see the results in the console when I use print. First, I'm creating the file which is working because it is created; the output file name is taken from a string, and that part is working too. So the following creates the file in the given path:
report_strategy = open(output_path+strategy.partition("strategy(")[2].partition(",")[0]+".txt", "w")
it creates a text file with the name taken from a string named "strategy", for example:
strategy = "strategy(abstraction,Ent_parent)"
a file called "abstraction.txt" is created in the output path folder. So far so good. But I can't get to write anything to this file. I have a range of a few integers
maps = (175,178,185)
This is the function:
def strategy_count(map_path,map_id)
The following loop does the counting for each item in the range "maps" to return an integer:
for i in maps:
report_strategy.write(str(i), ",", str(strategy_count(maps_path,str(i))))
and the file is closed at the end:
report_strategy.close()
Now the following:
for i in maps:
print str(i), "," , strategy_count(maps_path,str(i))
does give me what I want in the console:
175 , 3
178 , 0
185 , 1
What am I missing?! The function works, the file is created. I see the output in the console as I want, but I can't write the same thing in the file. And of course, I close the file.
This is a part of a program that reads text files (actually Prolog files) and runs an Answer Set Programming solver called Clingo. Then the output is read to find instances of occurring strategies (a series of actions with specific rules). The whole code:
import pmaps
import strategies
import generalization
# select the strategy to count:
strategy = strategies.abstraction_strategy
import subprocess
def strategy_count(path,name):
p=subprocess.Popen([pmaps.clingo_path,"0",""],
stdout=subprocess.PIPE,stderr=subprocess.STDOUT,stdin=subprocess.PIPE)
#
## write input facts and rules to clingo
with open(path+name+".txt","r") as source:
for line in source:
p.stdin.write(line)
source.close()
# some generalization rules added
p.stdin.write(generalization.parent_of)
p.stdin.write(generalization.chain_parent_of)
# add the strategy
p.stdin.write(strategy)
p.stdin.write("#hide.")
p.stdin.write("#show strategy(_,_).")
#p.stdin.write("#show parent_of(_,_,_).")
# close the input to clingo
p.stdin.close()
lines = []
for line in p.stdout.readlines():
lines.append(line)
counter=0
for line in lines:
if line.startswith('Answer'):
answer = lines[counter+1]
break
if line.startswith('UNSATISFIABLE'):
answer = ''
break
counter+=1
strategies = answer.count('strategy')
return strategies
# select which data set (from the "pmaps" file) to count strategies for:
report_strategy = open(pmaps.hw3_output_path+strategy.partition("strategy(")[2].partition(",")[0]+".txt", "w")
for i in pmaps.pmaps_hw3_fall14:
report_strategy.write(str(i), ",", str(strategy_count(pmaps.path_hw3_fall14,str(i))))
report_strategy.close()
# the following is for testing the code. It is working and there is the right output in the console
#for i in pmaps.pmaps_hw3_fall14:
# print str(i), "," , strategy_count(pmaps.path_hw3_fall14,str(i))
write takes one argument, which must be a string. It doesn't take multiple arguments like print, and it doesn't add a line terminator.
If you want the behavior of print, there's a "print to file" option:
print >>whateverfile, stuff, to, print
Looks weird, doesn't it? The function version of print, active by default in Python 3 and enabled with from __future__ import print_function in Python 2, has nicer syntax for it:
print(stuff, to, print, out=whateverfile)
The problem was with the write which as #user2357112 mentioned takes only one argument. The solution could also be joining the strings with + or join():
for i in maps:
report.write(str(i)+ ","+str(strategy_count(pmaps.path_hw3_fall14,str(i)))+"\n")
#user2357112 your answer might have the advantage of knowing if your test debug in the console produces the write answer, you just need to write that. Thanks.

error with string format operator %

Been researching this for a while now and can't seem to find a solution. I'm starting to think that there may not be one. Note, I am still self teaching myself Python, so I may be missing an easy solution.
Here's the line:
time = time.strftime("%Y%m%d-%H%M%S")
sys.stdout = open('/home/username/logfilesgohere/%s', % time 'w')
I'm trying to make the file that stdout writes to reflect the current timestamp as per the time variable. This would be easy to do in bash with backticks but I'm a python newb. Also to note that I'm using this in my fabfile for fabric.
You simply put the comma in the wrong place. This is what you want:
sys.stdout = open('/home/username/logfilesgohere/%s' % time, 'w')
This piece of code
'/home/username/logfilesgohere/%s' % time
will build the full filename string.

Python - writing lines from file into IRC buffer

Ok, so I am trying to write a Python script for XCHAT that will allow me to type "/hookcommand filename" and then will print that file line by line into my irc buffer.
EDIT: Here is what I have now
__module_name__ = "scroll.py"
__module_version__ = "1.0"
__module_description__ = "script to scroll contents of txt file on irc"
import xchat, random, os, glob, string
def gg(ascii):
ascii = glob.glob("F:\irc\as\*.txt")
for textfile in ascii:
f = open(textfile, 'r')
def gg_cb(word, word_eol, userdata):
ascii = gg(word[0])
xchat.command("msg %s %s"%(xchat.get_info('channel'), ascii))
return xchat.EAT_ALL
xchat.hook_command("gg", gg_cb, help="/gg filename to use")
Well, your first problem is that you're referring to a variable ascii before you define it:
ascii = gg(ascii)
Try making that:
ascii = gg(word[0])
Next, you're opening each file returned by glob... only to do absolutely nothing with them. I'm not going to give you the code for this: please try to work out what it's doing or not doing for yourself. One tip: the xchat interface is an extra complication. Try to get it working in plain Python first, then connect it to xchat.
There may well be other problems - I don't know the xchat api.
When you say "not working", try to specify exactly how it's not working. Is there an error message? Does it do the wrong thing? What have you tried?

Forcing a python script to take input from STDIN

A python script I need to run takes input only from a file passed as a command line argument, like so:
$ markdown.py input_file
Is there any way to get it to accept input from STDIN instead? I want to be able to do this through Bash, without significantly modifying the python script:
$ echo "Some text here" | markdown.py
If I have to modify the Python script, how would I go about it?
(EDIT: Here is the script that is parsing the command line options.)
I'm not sure how portable it is, but on Unix-y systems you can name /dev/stdin as your file:
$ echo -n hi there | wc /dev/stdin
0 2 8 /dev/stdin
Make sure this is near the top of the file:
import sys
Then look for something like this:
filename = sys.argv[1]
f = open(filename)
and replace it with this:
f = sys.stdin
It's hard to be more specific without seeing the script that you're starting with.
In the code you have a line like this:
if not len(args) == 1:
What you could do there is to check if you don't have a filename and instead either use "/dev/stdin" (on a system that allows it).
Another solution is to just replace:
if not len(args) == 1:
parser.print_help()
return None, None
else:
input_file = args[0]
with
if not len(args) == 1:
input_file = sys.stdin
else:
input_file = open(args[0])
That means of course that the returned "input_file" is no longer a file name but a file object, which means further modifications in the calling function.
First solution is less modifications but more platform specific, second is more work, but should work on more systems.
I'm guessing from the details of your question that you're asking about Python-Markdown, so I tracked down the relevant line in the source code for you: to do it Daniel's way, in line 443 of markdown/__init__.py, you'd want to replace
input_file = codecs.open(input, mode="r", encoding=encoding)
with
input_file = codecs.EncodedFile(sys.stdin, encoding)
Although then you wouldn't be able to actually process files afterwards, so for a more generally useful hack, you could put in a conditional:
if input:
input_file = codecs.open(input, mode="r", encoding=encoding)
else:
input_file = codecs.EncodedFile(sys.stdin, encoding)
and then you'd have to adjust markdown/commandline.py to not quit if it isn't given a filename: change lines 72-73
parser.print_help()
return None, None
to
input_file = None
The point is, it's not really a simple thing to do. At this point I was going to suggest using a special file like Mark Rushakoff did, if he hadn't beaten me to it ;-)
I suggest going here:
http://codaset.com/repo/python-markdown/tickets/new
And submitting a ticket requesting them to add the feature. It should be straightforward for them and so they might be willing to go ahead and do it.
In bash, you can also use process substitution:
markdown.py <(echo "Some text here")
For a single input /dev/stdin works, but process substitution also applies for several inputs (and even outputs)

Categories

Resources