Reputation: 53
I want to use a function as a default argument for another function that can be overridden without calling the function.
Let's use this snippet:
def split():
word = input('Whats the word?')
return [letter for letter in word]
def do_something_with_letters(letters=split())
for letter in letters:
print(letter)
If I call do_something_with_letters like this:
do_somthing_with_letters()
The program works how I would expect it to but not in the order that I expect. The end result is that split is called and user input is used to define the word which is split then passed into do_something_with_letters. Now, this is happening during the declaration of do_somthing_with_letters rather than during the function call(where split() is used as a default value).
for instance if I override the default value i.e:
do_somthing_with_letters(['a', 'b', 'c'])
The following chain of events occurs: Split Declared > do_somthing_with_letters Declared > Split Called and assigned to letters(or stored in memory) > do_somthing_with_letters called with overridden value.
The user has been needlessly prompted for input when it should have been over-written.
Again I need a function to be the default value for letters any answer should have a way of keeping that.
Upvotes: 0
Views: 134
Reputation: 552
I believe this sort of problem may call for a decorator. You can define a function, such as verbose
(shown below), that when used to decorate a function that returns an iterable, modifies it according to the behavior as specified in do_something_with_letters
from your post.
Then by simply decorating the split
function, you can achieve the desired result.
def verbose(f):
def func(s):
for ch in f(s):
print(ch)
return func
@verbose
def split(s):
return (ch for ch in s)
if __name__ == '__main__':
s = input("Enter word: ")
split(s)
Now any other function may be modified in a similar way. For example, the upper_and_split
function will print all characters in s in uppercase.
@verbose
def upper_and_split(s):
return (ch for ch in s.upper())
Upvotes: 0
Reputation: 87074
Now, this is happening during the declaration of do_somthing_with_letters rather than during the function call(where split() is used as a default value).
Because the function split()
is being called when the function is declared. You can initialise letters
with the function name (actually the function object) without calling it by omitting the parentheses. Then you can test if the argument can be called, e.g because it is a function, callable class etc.
def do_something_with_letters(letters=split):
if callable(letters):
letters = letters()
for letter in letters:
print(letter)
Now if you call do_something_with_letters()
without arguments, letters
will default to the split()
function and call it to get the letters to work on. If you were to pass a string or list then it would print the elements of those objects. You could even pass in a different function to have it obtain the input.
>>> do_something_with_letters()
Whats the word?hello
h
e
l
l
o
>>> do_something_with_letters('abcd')
a
b
c
d
>>> do_something_with_letters(lambda : 'a string')
a
s
t
r
i
n
g
>>> do_something_with_letters(range(5)) # not letters at all
0
1
2
3
4
Upvotes: 1
Reputation: 77837
You have a counter-intuitive design, combining program steps that aren't functionally related. As a result, you're trying to warp the module design to compensate. user input and pre-processing the input are not fully linked in your program design -- so why do you insist on putting them into a module where they are linked? Decouple those steps.
Your do_something
function should not have to adapt to wherever the string originates. let it simply handle its string argument.
If you somehow do have a design that requires this contortion, you have a problem: the default value must be realized at the definition of do_something
.
You can leave the function itself as an argument:
def do_something(source=split):
if not isstring(source):
letters = source(argument) # You still need to supply argument
However, this is still tortuous design.
Also, I strongly recommend that you not use split
as a function name, since that is already a built-in string function.
Upvotes: 1