common practice for creating utility functions?

J

John Salerno

Just a quickie for today: Is it common (and also preferred, which are
two different things!) to create a function that has the sole job of
calling another function?

Example: for fun and exercise, I'm creating a program that takes a quote
and converts it into a cryptogram. Right now I have four functions:

convert_quote -- the main function that starts it all
make_code -- makes and returns the cryptogram
make_set -- called from make_code, converts the quote into a set so each
letter gets only one coded letter
test_sets -- makes sure a letter isn't assigned to itself

So my first question is this: should I make a Cryptogram class for this,
or are functions fine? If the latter, then back to my original point:
can I do something like this:

def convert_quote(quote):
return make_code(quote)

Or does it not make sense to have a function just call another function?
 
P

Paul Rubin

John Salerno said:
So my first question is this: should I make a Cryptogram class for
this, or are functions fine? If the latter, then back to my original
point: can I do something like this:

def convert_quote(quote):
return make_code(quote)

It's fine to do that if it expresses your intentions more clearly.
It's also normal to write a call for a function you haven't written
yet:

some_quote = convert_quote(another_quote)

where you write convert_quote afterwards. It may not be til you
actually write convert_quote that you notice that it simply calls
another function. If that happens, it's fine.

Also, you don't need to make a class if you don't need to do OOP-like
things with the instances. But, sometimes, making a class also
clarifies your intentions. For example, making a class means it
becomes much simpler to have multiple cryptograms active at the same
time, if you want to do that.

Programming is not just creating strings of instructions for a
computer to execute. It's also "literary" in that you are trying to
communicate a program structure to other humans reading the code.

Someone once asked the famous bike racing champion Eddy Merckx (he was
sort of the Lance Armstrong of the 1970's and then some) how to become
a good racer. His answer was "ride a lot". It's the same way with
coding. As you get more experienced these questions get easier to
answer for yourself. But always, do whatever works, and if your code
gets messy or hits technical snags, ask yourself what alternative
approaches avoid those snags, and thereby develop a sense of what to
do.
 
D

Dan Sommers

So my first question is this: should I make a Cryptogram class for
this, or are functions fine? ...

Perhaps I'm "old school," but I don't bother with classes unless I'm
going to end up with multiple instances (or I'm pushed into a corner by,
e.g., a GUI framework).
... If the latter, then back to my original point: can I do something
like this:
def convert_quote(quote):
return make_code(quote)

Of course you can. Or, since this is python, you can also do this:

convert_quote = make_quote
Or does it not make sense to have a function just call another
function?

If there's a good design-level reason (like keeping certain objects or
classes unaware of others, or leaving room for something you know you
will add later), then there's nothing wrong with a function consisting
solely of another function call. If you end up with a lot of those tiny
functions, though, and they persist through multiple development cycles,
then you may be making a systematic mistake in your design.

Regards,
Dan
 
S

Scott David Daniels

John said:
...Is it common ...[and preferred] to create a function that has the sole job of
calling another function?

Example: ... cryptogram. Right now I have four functions:

convert_quote -- the main function that starts it all
make_code -- makes and returns the cryptogram
make_set -- called from make_code, converts the quote into a set so each
letter gets only one coded letter
test_sets -- makes sure a letter isn't assigned to itself

So my first question is this: should I make a Cryptogram class for this,
or are functions fine?
Functions are just fine. I'd use a class if they wanted to share state.
... can I do something like this:
def convert_quote(quote):
return make_code(quote)
Or does it not make sense to have a function just call another function?
Obviously you _can_ do that. I wouldn't, however. If (to you) the four
functions above "mean" something different, I'd implement convert_quote
with:
convert_quote = make_code
 
J

John Salerno

John said:
Just a quickie for today

Another quick one I'll combine in this thread: How can I create two
separate conditions in a for loop?

Such as this, which doesn't seem to work beyond string.punctuation:

for char in string.punctuation or string.whitespace:
 
J

John Salerno

Dan said:
Perhaps I'm "old school," but I don't bother with classes unless I'm
going to end up with multiple instances (or I'm pushed into a corner by,
e.g., a GUI framework).

Thanks to all of you! In fact, I sort of came to this same conclusion. I
started with a class, but realized there wasn't much of a point since
I'm just doing some 'work' and returning the cryptogram.

I might try the other suggestion too about convert_quote = make_code,
that might be ok. Or I can simply call make_code.

Thanks!
 
J

John Salerno

John said:
Another quick one I'll combine in this thread: How can I create two
separate conditions in a for loop?

Such as this, which doesn't seem to work beyond string.punctuation:

for char in string.punctuation or string.whitespace:

I tried this:

punc_space = string.punctuation + string.whitespace
for char in punc_space:
 
B

BartlebyScrivener

QOTW

"Programming is not just creating strings of instructions for a
computer to execute. It's also 'literary' in that you are trying to
communicate a program structure to other humans reading the code." Paul
Rubin

rpd
 
S

Scott David Daniels

John said:
...
I tried this:

punc_space = string.punctuation + string.whitespace
for char in punc_space:

That's probably best. If the sources are not so simple, you could use:

import itertools

for char in itertools.chain(onesource, anothersource, yetanother):
...

--Scott David Daniels
(e-mail address removed)
 
J

John Salerno

Scott said:
That's probably best. If the sources are not so simple, you could use:

import itertools

for char in itertools.chain(onesource, anothersource, yetanother):
...

--Scott David Daniels
(e-mail address removed)

Cool. Iterators are fun! :)
 
J

John Salerno

Alrighty, here is what I've come up with. Any suggestions for tweaks or
speed-boosting efficiency? Just kidding. :)

Maybe the weakest part is how the code is determined (just shuffling the
letters in the alphabet). Perhaps there's a better way? Although it
seems effective to me....


import string
import random
import itertools

def convert_quote(quote):
coded_quote = make_code(quote)
author = coded_quote.split('|')[1]
quote = coded_quote.split('|')[0]
return quote, author

def make_code(original):
original_letters = make_set(original)
new_letters = list(string.ascii_uppercase)
while True:
random.shuffle(new_letters)
trans_letters = ''.join(new_letters)[:len(original_letters)]
if test_code(original_letters, trans_letters):
trans_table = string.maketrans(original_letters, trans_letters)
break
return original.translate(trans_table)

def make_set(original):
original_set = set(original)
punc_space = string.punctuation + string.whitespace
for char in punc_space:
if char in original_set:
original_set.remove(char)
return ''.join(original_set)

def test_code(original_letters, trans_letters):
for pair in itertools.izip(original_letters, trans_letters):
if pair[0] == pair[1]:
return False
return True

if __name__ == '__main__':
print convert_quote("The past is not dead. In fact, it's not even
past.|William Faulkner")
 
P

Paul Rubin

John Salerno said:
def convert_quote(quote):
coded_quote = make_code(quote)
author = coded_quote.split('|')[1]
quote = coded_quote.split('|')[0]
return quote, author

I think it's a little bit ugly (plus inefficient) to split the quote twice.
You can use:

def convert_quote(quote):
coded_quote = make_code(quote)
author, quote = coded_quote.split('|')
return quote, author
def make_code(original):
original_letters = make_set(original)
new_letters = list(string.ascii_uppercase)
while True:
random.shuffle(new_letters)
trans_letters = ''.join(new_letters)[:len(original_letters)]
if test_code(original_letters, trans_letters):
trans_table = string.maketrans(original_letters, trans_letters)
break
return original.translate(trans_table)

You're trying to make sure that no character maps to itself in the
cryptogram. I'm not sure if that's one of the "rules". If not, you
might like to know that it's a cryptographic weakness, not that you're
attempting strong cryptography ;-). But the WW2 Enigma machine had
that characteristic as part of its design, and that was used to break it.

It also looks like upper and lower case letters in the input are
treated as separate. For example, "George" become "abcdef" while
"george" becomes "abcde". Did you want that? I don't think it can be
right, because "trans_letters" has at most 26 characters, but there
can be 52 separate original letters (26 upper and 26 lower).

As for the efficiency of the above algorithm, well, look at what
happens after you shuffle the alphabet: the probability that any given
character maps to something other than itself is 25/26. That means
the probability that N letters all map to something other than
themselves is (25/26)**N. If N=26, this is about 0.36, so on average
you'll shuffle about three times, which is not too bad, if you're just
doing something casual. Note that this is close to 1/e. I'll let you
figure out the reason. Of course there is a chance you'll have to
shuffle 4 times, 10 times, 1000 times, etc. You might like to
calculate those probabilities and decide whether it's worth thinking
up a more efficient algorithm, that's possibly more complex and
therefore more likely to have bugs, to burn additional development
time, etc. How bad is it if occasional instances take a long time to
generate? This is the kind of tradeoff you always have to spend time
pondering if you're doing a large scale application.
def make_set(original):
original_set = set(original)
punc_space = string.punctuation + string.whitespace
for char in punc_space:
if char in original_set:
original_set.remove(char)
return ''.join(original_set)

I think I'd write something like:

def make_set(original):
return set(strings.ascii_uppercase) & set(original.upper())

That is, convert the original string to uppercase, make a set from it,
and then intersect that set with the set of uppercase letters. This
seems more direct.
 
P

Paul Rubin

Bruno Desthuilliers said:
def convert_quote(quote):
return make_code(quote).split('|', 1)

I thought about suggesting that, but it's semantically different than
the original since it fails to raise an error if no vertical bar is
present. I don't know if that's good or bad.
 
B

Bruno Desthuilliers

John Salerno a écrit :
Just a quickie for today: Is it common (and also preferred, which are
two different things!) to create a function that has the sole job of
calling another function?
(pasted):
> def convert_quote(quote):
> return make_code(quote)
>
> Or does it not make sense to have a function just call another function?

The only use case I can think of for this extra level of indirection
would be to isolate your code from an implementation detail that may
change later. But since it's quite easy to do so by just binding the
original function to a new name, your example's sole result is to slow
down your program.

It may also happen that you find yourself in such a situation due to
heavy refactoring, and cannot immediatly get rid of convert_quote
without breaking client code, but then again, the Right Thing (tm) to do
would be to alias it:

# def convert_quote(quote):
# return make_code(quote)
convert_quote = make_quote

So to answer your question, it's certainly not prefered, and hopefully
very uncommon !-)

Example: for fun and exercise, I'm creating a program that takes a quote
and converts it into a cryptogram. Right now I have four functions:

convert_quote -- the main function that starts it all
make_code -- makes and returns the cryptogram
make_set -- called from make_code, converts the quote into a set so each
letter gets only one coded letter
test_sets -- makes sure a letter isn't assigned to itself

So my first question is this: should I make a Cryptogram class for this,
or are functions fine?

classes are useful to maintain state. If you don't have a state to
maintain, you probably don't need a class !-)

Note BTW that Python functions are objects (instances of class
function), and that Python let you write your own callables (just make
your class implement the __call__() magic method).

My 2 cents
 
B

Bruno Desthuilliers

Paul Rubin a écrit :
It's fine to do that if it expresses your intentions more clearly.

Then it would be better to just alias it:

# def convert_quote(quote):
# return make_code(quote)
convert_quote = make_code

About the "fine to do" part, remember that Python's function calls are
rather expansive...
 
B

Bruno Desthuilliers

John Salerno a écrit :
I tried this:

punc_space = string.punctuation + string.whitespace
for char in punc_space:

Or if you want to save memory (which is not a problem in this example) :

import itertools
for char in itertools.chain(string.punctuation, string.whitespace):
...
 
B

Bruno Desthuilliers

John Salerno a écrit :
Alrighty, here is what I've come up with. Any suggestions for tweaks or
speed-boosting efficiency? Just kidding. :)

Too late ! You asked for it, you get it !-)
Maybe the weakest part is how the code is determined (just shuffling the
letters in the alphabet). Perhaps there's a better way? Although it
seems effective to me....


import string
import random
import itertools

def convert_quote(quote):
coded_quote = make_code(quote)
author = coded_quote.split('|')[1]
quote = coded_quote.split('|')[0]
return quote, author

def convert_quote(quote):
return make_code(quote).split('|', 1)
def make_code(original):
original_letters = make_set(original)
new_letters = list(string.ascii_uppercase)
while True:
random.shuffle(new_letters)
trans_letters = ''.join(new_letters)[:len(original_letters)]
if test_code(original_letters, trans_letters):
trans_table = string.maketrans(original_letters, trans_letters)
break
return original.translate(trans_table)

def make_set(original):
original_set = set(original)
punc_space = string.punctuation + string.whitespace
for char in punc_space:
if char in original_set:
original_set.remove(char)
return ''.join(original_set)

PUNCT_SPACE_SET = set(string.punctuation + string.whitespace)
def make_set(original):
return ''.join(set(original) - PUNCT_SPACE_SET)

<splitting-hairs>
BTW, the name make_set is a bit misleading, since the function returns a
string... FWIW, some other functions could benefit from better naming too:
- convert_quote() takes a "<quote>|<author>" string and returns a
(<coded_quote>, <coded_author>) pair (given the name, I would expect a
"<coded_quote>| <coded_author>" string as output)
- make_code() doesn't actually 'make a code' - it really encrypt it's input
def test_code(original_letters, trans_letters):
for pair in itertools.izip(original_letters, trans_letters):
if pair[0] == pair[1]:
return False
return True

If you really want "speed-boosting efficiency", you may try this instead:
def test_code(original_letters, trans_letters)
# assert len(original_letters) == len(trans_letters), \
# "original_letters and trans_letters should be of equal len"
for i in range(original_letters):
if original_letters == trans_letters:
return False
return True
if __name__ == '__main__':
print convert_quote("The past is not dead. In fact, it's not even
past.|William Faulkner")

My 2 cents...
 
B

Bruno Desthuilliers

Paul Rubin a écrit :
I thought about suggesting that, but it's semantically different than
the original since it fails to raise an error if no vertical bar is
present.

Yeps, true.
I don't know if that's good or bad.

At least someone reading this may learn about the max_split param of
str.split() !-)
 
J

John Salerno

Paul said:
You're trying to make sure that no character maps to itself in the
cryptogram. I'm not sure if that's one of the "rules".

It's a rule for cryptogram puzzles, which is what I'm working on. I'm
not trying to make complicated hacker-proof codes or anything. :)
It also looks like upper and lower case letters in the input are
treated as separate. For example, "George" become "abcdef" while
"george" becomes "abcde". Did you want that? I don't think it can be
right, because "trans_letters" has at most 26 characters, but there
can be 52 separate original letters (26 upper and 26 lower).

You're absolutely right. I forgot about this when I changed some of the
stuff in the convert_quote function. Originally I did convert the
original quote to uppercase first, and I forgot to keep that when I made
some changes.
As for the efficiency of the above algorithm, well, look at what
happens after you shuffle the alphabet: the probability that any given
character maps to something other than itself is 25/26. That means
the probability that N letters all map to something other than
themselves is (25/26)**N. If N=26, this is about 0.36, so on average
you'll shuffle about three times, which is not too bad, if you're just
doing something casual.

It seems okay so far. I should feed it a really long quote just to see
how it acts, but aside from that there won't be much heavy processing
going on.

Thanks for the tips!
 
J

John Salerno

Bruno said:
Too late ! You asked for it, you get it !-)

Good, I desperately need it! :)
PUNCT_SPACE_SET = set(string.punctuation + string.whitespace)
def make_set(original):
return ''.join(set(original) - PUNCT_SPACE_SET)

Interesting! Always learning new, weird ways to do things!
<splitting-hairs>

Nope, I was actually wondering about what to name some of my functions
and variables.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top