Skip to content

Commit 736b643

Browse files
committed
abuse discussion
1 parent b87a615 commit 736b643

14 files changed

Lines changed: 464 additions & 1135 deletions

File tree

abuse/Makefile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
.PHONY: pdf test
2-
3-
pdf:
4-
pandoc README.md -o README.pdf
1+
.PHONY: test
52

63
test:
7-
pytest -v test.py
4+
pytest -xv test.py

abuse/README.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
# Abuse
22

3-
Write a Python program called `abuse.py` that generates some `-n|--number` of insults (default `3`) by randomly combining some number of `-a|--adjectives` (default `2`) with a noun (see below). Be sure your program accepts a `-s|--seed` argument (defualt `None`) to pass to `random.seed`.
3+
Write a Python program called `abuse.py` that generates some `-n|--number` of insults (default `3`) by randomly combining some number of `-a|--adjectives` (default `2`) with a noun (see below). Be sure your program accepts a `-s|--seed` argument (default `None`) to pass to `random.seed`.
44

5-
Adjectives:
5+
The are the adjectives you should use:
66

77
bankrupt base caterwauling corrupt cullionly detestable dishonest
88
false filthsome filthy foolish foul gross heedless indistinguishable
99
infected insatiate irksome lascivious lecherous loathsome lubbery old
1010
peevish rascaly rotten ruinous scurilous scurvy slanderous
1111
sodden-witted thin-faced toad-spotted unmannered vile wall-eyed
1212

13-
Nouns:
13+
And these are the nouns:
1414

1515
Judas Satan ape ass barbermonger beggar block boy braggart butt
1616
carbuncle coward coxcomb cur dandy degenerate fiend fishmonger fool
1717
gull harpy jack jolthead knave liar lunatic maw milksop minion
1818
ratcatcher recreant rogue scold slave swine traitor varlet villain worm
1919

20+
If run with the `-h|--help` flag, the program should generate usage:
21+
2022
````
2123
$ ./abuse.py -h
2224
usage: abuse.py [-h] [-a int] [-n int] [-s int]
@@ -29,10 +31,20 @@ optional arguments:
2931
Number of adjectives (default: 2)
3032
-n int, --number int Number of insults (default: 3)
3133
-s int, --seed int Random seed (default: None)
34+
````
35+
36+
When run with no arguments, the program should generate insults using the defaults:
37+
38+
````
3239
$ ./abuse.py
3340
You slanderous, rotten block!
3441
You lubbery, scurilous ratcatcher!
3542
You rotten, foul liar!
43+
````
44+
45+
It's unlikely you'll get the same output above when you run yours because no seed was set. The following, however, should be exactly reproducible due to the `--seed`:
46+
47+
````
3648
$ ./abuse.py -s 1 -n 2 -a 1
3749
You rotten rogue!
3850
You lascivious ape!
@@ -42,3 +54,20 @@ You cullionly, lubbery, heedless, filthy lunatic!
4254
You foul, lecherous, infected, slanderous degenerate!
4355
You base, ruinous, slanderous, false liar!
4456
````
57+
58+
If run with a `--number` less than 1, exit with an error code and message, preferably with the usage:
59+
60+
````
61+
$ ./abuse.py -n -4
62+
usage: abuse.py [-h] [-a int] [-n int] [-s int]
63+
abuse.py: error: --number "-4" cannot be less than 1
64+
````
65+
66+
Hints:
67+
68+
* You can use three single or double quotes (\"\"\") to create a multi-line string and then `split()` that to get a list of strings. This is easier than individually quoting a long list of shorter strings (e.g., the list of adjectives and nouns).
69+
* Perform the check for `--number` inside the `get_args` function and use `parser.error` to throw the error while printing a message and the usage.
70+
* If you set the default for `args.seed` to `None` while using a `type=int`, you should be able to directly pass the argument's value to `random.seed` to control testing.
71+
* Use a `for` loop with the `range` function to create a loop that will execute `--number` of times to generate each insult.
72+
* Look at the `sample` and `choice` functions in the `random` module for help in selecting some adjectives and a noun.
73+
* To construct an insult string to print, you can use the `+` operator to concatenate strings, use the `str.join` method, or use format strings (and maybe other methods?).

abuse/abuse.py

Lines changed: 0 additions & 71 deletions
This file was deleted.

abuse/discussion.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
![Erak! The captain is a corrupt, irksome fiend!](images/parrot.png)
2+
3+
## get_args
4+
5+
More than half of my solution is just in defining the program's arguments to `argparse`. The effort is well worth the result, because `argparse` will ensure that each argument is a valid integer value because I set `type=int`. Notice there are no quotes around the `int` -- it's not the string `'int'` but a reference to the class in Python. You can use the `type` function in Python to find out how Python represents a value:
6+
7+
````
8+
>>> type(int)
9+
<class 'type'>
10+
>>> type('int')
11+
<class 'str'>
12+
````
13+
14+
For `--adjectives` and `--number`, I can set reasonable defaults so that no input is *required* from the user but the values are easily overridden. This makes your program dynamic, interesting, and testable. How do you know if your values are being used correctly unless you change them and test that the proper change was made in your program. Maybe you started off hardcoding the number of insults and forgot to change the `range` to use a variable. Without changing the input value and testing that the number of insults changed accordingly, it might be a user who discovers your bug, and that's somewhat embarrassing.
15+
16+
Another reason I quite like `argparse` is that, if I find there is a problem with an argument, I can use `parser.error` to do four things:
17+
18+
1. Print the short usage of the program to the user
19+
2. Print a specific message about the problem
20+
3. Halt execution of the program
21+
4. Return an error code to the operating system
22+
23+
For instance, I can't very easily tell `argparse` that the `--number` should be a positive integer, only that it must be of type `int`. I can, however, inspect the value myself and call `parser.error('message')` if there is a problem. I do all this inside `get_args` so that, by the time I call `args = get_args()` in my `main` function, I know that all the arguments have been validated. I could have also added a similar check for `--adjectives`, but the main point was to highlight that such a thing is possible. As you write your own programs, you'll have to decide how much validation of user input you feel is necessary.
24+
25+
## main
26+
27+
Once I'm in `main` and have my arguments, I can control the randomness of the program by calling `random.seed(args.seed)` because:
28+
29+
1. The default value of the `seed` is `None`, and setting `random.seed` to `None` is the same as not setting it at all.
30+
2. The `type` of `args.seed` is `int` which is the proper type for `random.seed`. I do not have to validate the argument further. Negative integers are valid values.
31+
32+
To generate some `--number` of insults, I use the `range` function. Because I don't need the number of the insult, I can use the underscore (`_`) as a throwaway value:
33+
34+
````
35+
>>> num_insults = 2
36+
>>> for _ in range(num_insults):
37+
... print('An insult!')
38+
...
39+
An insult!
40+
An insult!
41+
````
42+
43+
The underscore is a way to unpack a value and indicate that you do not intend to use it. That is, it's not possible to write this:
44+
45+
````
46+
>>> for in range(num_insults):
47+
File "<stdin>", line 1
48+
for in range(num_insults):
49+
````
50+
51+
You have to put *something* after the `for` that looks like a variable. If you put a named variable like `n` and then don't use it in the loop, some tools like `pylint` will detect this as a possible error (and well it could be). The `_` shows that you won't use it, which is good information for your future self, some other user, or external tools to know.
52+
53+
You can use multiple `_`, e.g., here I can unpack a 3-tuple so as to get the middle value:
54+
55+
````
56+
>>> x = 'Jesus', 'Mary', 'Joseph'
57+
>>> _, name, _ = x
58+
>>> name
59+
'Mary'
60+
````
61+
62+
To create my list of adjectives, I used the `str.split` method on a long, multi-line string I created using three quotes:
63+
64+
````
65+
>>> adjectives = """
66+
... bankrupt base caterwauling corrupt cullionly detestable dishonest
67+
... false filthsome filthy foolish foul gross heedless indistinguishable
68+
... infected insatiate irksome lascivious lecherous loathsome lubbery old
69+
... peevish rascaly rotten ruinous scurilous scurvy slanderous
70+
... sodden-witted thin-faced toad-spotted unmannered vile wall-eyed
71+
... """.strip().split()
72+
>>> nouns = """
73+
... Judas Satan ape ass barbermonger beggar block boy braggart butt
74+
... carbuncle coward coxcomb cur dandy degenerate fiend fishmonger fool
75+
... gull harpy jack jolthead knave liar lunatic maw milksop minion
76+
... ratcatcher recreant rogue scold slave swine traitor varlet villain worm
77+
... """.strip().split()
78+
>>> len(adjectives)
79+
36
80+
>>> len(nouns)
81+
39
82+
````
83+
84+
To select some number of adjectives, I chose to use `random.sample` function since I needed more than one:
85+
86+
````
87+
>>> import random
88+
>>> random.sample(adjectives, k=3)
89+
['filthsome', 'cullionly', 'insatiate']
90+
````
91+
92+
For just one randomly selected value, I use `random.choice`:
93+
94+
````
95+
>>> random.choice(nouns)
96+
'boy'
97+
````
98+
99+
To concatenante them together, I need to put `', '` (a comma and a space) between each of the adjectives, and I can use `str.join` for that:
100+
101+
````
102+
>>> adjs = random.sample(adjectives, k=3)
103+
>>> adjs
104+
['thin-faced', 'scurvy', 'sodden-witted']
105+
>>> ', '.join(adjs)
106+
'thin-faced, scurvy, sodden-witted'
107+
````
108+
109+
And feed all this to a format string:
110+
111+
````
112+
>>> noun = random.choice(nouns)
113+
>>> print('You {} {}!'.format(', '.join(adjs), noun))
114+
You thin-faced, scurvy, sodden-witted liar!
115+
````
116+
117+
And now you have a handy way to make enemies and influence people.

abuse/solution.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python3
2+
"""Heap abuse"""
23

34
import argparse
45
import random
@@ -23,8 +24,9 @@
2324
# --------------------------------------------------
2425
def get_args():
2526
"""get command-line arguments"""
27+
2628
parser = argparse.ArgumentParser(
27-
description='Argparse Python script',
29+
description='Heap abuse',
2830
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
2931

3032
parser.add_argument('-a',
@@ -48,16 +50,21 @@ def get_args():
4850
type=int,
4951
default=None)
5052

51-
return parser.parse_args()
53+
args = parser.parse_args()
54+
55+
if args.number < 1:
56+
parser.error('--number "{}" cannot be less than 1'.format(args.number))
57+
58+
return args
5259

5360

5461
# --------------------------------------------------
5562
def main():
5663
"""Make a jazz noise here"""
64+
5765
args = get_args()
5866
num_adj = args.adjectives
5967
num_insults = args.number
60-
6168
random.seed(args.seed)
6269

6370
for _ in range(num_insults):

0 commit comments

Comments
 (0)