I'm new to learning Python and have enough under my belt to start attempting a beginner's Tic-Tac-Toe program.
My issue is thus: I want to have a generic input function called getInput() which will get input from the user, strip trailing white space from that input, and THEN, if a function was passed to it via the optional parameter "specialTest", getInput() will run the input through this provided function and return the output which the specialTest function spat out.
Sometimes this specialTest function will need additional arguments besides the user input. Assume for my purposes that the user input will always be the first argument and is required, and that any additional args will come afterwards.
I tried to implement this situation via *args, and I got it working if the specialTest function had no additional arguments. But the first time I try to feed it additional arguments, it fails.
So for example, getInput("Age?", specialTest=int) works. It prompts for user input and feeds it through the int() function, finally returning the output as an integer. But when I try to pass getInput() a function which has an additional argument - an ordered dictionary which contains strings as keys and dictionaries as values - the program fails with TypeTypeError: getInput() got multiple values for argument 'specialTest'. What needs to be adjusted to get this working as intended?
Code:
import collections
def getInput(msg, specialTest=None, *TestArgs):
"""Get user input and export to the desired format."""
while True:
string = input(msg + ' ').strip()
# If the user passed a function to the SpecialTest parameter,
# pass the user input through that function and return its value.
# If the SpecialTest function returns False or we hit an error,
# that means the input was invalid and we need to keep looping
# until we get valid input.
if specialTest:
try:
string = specialTest(string, *TestArgs)
if string is False: continue
except:
continue
return string
def nametoMove(name, board):
"""Convert player's move to an equivalent board location."""
location = {name: theBoard.get(name)}
# return false if the location name isn't present on the board
if location[name] is None:
return False
return location
# ---Tic-Tac-Toe routine---
# fill the board
row_name = ('top', 'mid', 'lower')
col_name = ('left', 'center', 'right')
theBoard = collections.OrderedDict()
size = 3 # 3x3 board
for x in range(size):
for y in range(size):
key = row_name[x] + ' ' + col_name[y]
value = {'row': x, 'col': y}
theBoard.update({key: value})
# get player's desired board symbol
playerSymbol = getInput("X's or O's?")
# get player's age
playerAge = getInput("Age?", specialTest=int)
# get player's move and convert to same format as theBoard object
# e.g., "top left" --> {'top left': {'row': 0, 'col': 0}}
playerMove = getInput("Move?", specialTest=nametoMove, *theBoard)
In order to support supplying the same parameter via a positional or keyword argument, Python converts any keyword arguments that can be into positional arguments. That creates the conflict in your example. Syntactically, what you want can be achieved by simply omitting the argument:
playerMove = getInput("Move?", nametoMove, *theBoard)
Or you can resolve the ambiguity with a “keyword-only” argument:
def getInput(msg, *TestArgs , specialTest=None):
Then the keyword argument cannot be converted, so there is no collision. (This can be emulated in Python 2 by using **kw to accept arbitrary keyword arguments and then checking that only the expected one is actually provided.)
But the question you should be asking is “How can I preset some arguments to a function used as a callback?”, to which the answer is either a lambda:
playerMove = getInput("Move?", specialTest=lambda s: nametoMove(s, *theBoard))
or functools.partial:
playerMove = getInput("Move?", specialTest=functools.partial(nametoMove, board=theBoard))
With either of these, you don’t need TestArgs at all. The partial approach doesn’t support supplying trailing positional arguments (like varargs), but your nametoMove doesn’t actually want those anyway (as established in the comments). So in all the approaches above you omit the *.
Related
Let's consider a function that accepts multiple arguments as under:
def my_function(*args):
my_list = []
for _ in args:
my_list.append(_)
another_function(my_list)
Now, the issue that I face in my scenario is that: I need my_list to contain at least one value, any argument, but at least one.
A user can do my_function(arg1), my_fuynction(arg1,arg2), my_function(arg1,arg2,arg3) and so on. But if a user does my_function(), I need to provide a default argument (say arg1) to the function.
If I put an default argument, then the argument defaultarg will be compulsory and it has to be supplied with some value:
def my_function(defaultarg, *args):
#function
If I put it as optional argument, then I will have to provide a default value to the argument:
def my_function(optionalarg = defaultvalue, *args):
#function
Both these ways do not work for me as I can't make it compulsory and I can't give a default value for it.
How do I create this function so that if no arguments are passed, the function assumes one argument to have been passed?
As of now, I am handling this by putting a if...else in the function as under:
def my_function(*args):
my_list = []
if len(args) == 0:
my_list.append('defaultarg')
else:
for _ in args:
my_list.append(_)
another_function(my_list)
Is there any better way to do this?
I can't give a default value for it
I don't understand this part of your question. You are providing the default value 'defaultarg' in your own solution?! My answer assumes that you can get the default value from somewhere.
Anyway, if you want to keep the f(*args) signature, you can check whether the args tuple is empty and supply a default value.
def my_function(*args):
if not args:
args = (default_value,)
# more code
or
def my_function(*args):
if not args:
return my_function(default_value)
# more code
You won't get around explicitly checking whether args is empty, but maybe you'll like one of these proposals better than your version.
edit:
You could also write a parameterized decorator.
def with_default_value(default_value):
def with_default_value_decorator(f):
def f_new(*args):
if not args:
return f(default_value)
return f(*args)
return f_new
return with_default_value_decorator
Example:
#with_default_value('hi')
def my_print(*args):
print(' '.join(args))
Demo:
>>> my_print('hello', 'world')
hello world
>>> my_print()
hi
So I am trying to make a script that will traverse a string and replace certain characters. The idea is fairly simple and sudo code looks a little like this.
Input1 = ''
Input2 = ''
Input3 = ''
rawPw = Input1 + Input2 + Input 3
Remove spaces if any exist
Creates a new empty str called finalPw
Scan the rawPw string one character at a time. Each character goes to a Random bool and if True it goes to get converted. If false it appends finalPw
In it goes to conversion it checks against a list of specified characters and if it matches it goes to a specific converter method. Otherwise it will just swap upper/lower case and append finalPw
The specific converter method will use a predefined list of characters that it can be replaced by, and will use random.choice() to pick from that list to replace the character and append finalPw.
This is the code I have so far, note the upper/lower case swapping is not part of it yet, I am building and testing the code piecemeal because my last prototype was 350 lines long and a complete failure. So going from scratch here.
import random
print("Enter 3 words or series of numbers or both, each entry must be at least 5 characters in length")
def main():
input1 = 'Jim'
input2 = 'Samantha'
input3 = 'Ethan'
rawPw = (input1 + input2 + input3)
refinedPw = rawPw.replace(' ', '')
print(refinedPw)
finalPw = ''
convertTrain(refinedPw)
def switch():
switchVal = random.choice(True, False)
return switchVal
def convertTrain():
temp = main.refinedPw
onOff = False
for i in temp:
switch(i)
if i == True:
if i == 'i':
the_iExc(i)
else:
main.finalPw.append(i)
def the_iExc():
rep_iExc = ['i', '!']
repVal = random.choice(rep_iExc)
for i in len(main.refinedwPw):
slice(i)
if i == 'i':
i.replace(repVal)
return i
main()
The error I am receiving is :
line 15, in main
convertTrain(refinedPw)
TypeError: convertTrain() takes 0 positional arguments but 1 was given
I have tried changing things around a bit to see if I can't fix the positional argument, so much so that I have stripped it of all classes and am just going classless until I get it to work. For the life of me I can't seem to get it to use the appropriate number of args.
This line defines convertTrain as a function that takes no arguments:
def convertTrain():
But then inside main() you call it with an argument:
convertTrain(refinedPw)
Either change the function definition to accept an argument, or change the call to not pass an argument.
You're trying to invoke function with arguments here:
convertTrain(refinedPw)
...but there are no arguments in the function's definition
def convertTrain()
To fix this, add a new argument for the convertTrain function, like this:
def convertTrain(train) # Name of argument can be changed if needed
Read more about function declaration here: click this
I am new here and new to programming (I have only been programming for 2 weeks). Does anyone have any suggestions on how to test a function that takes no parameters. I have tested functions that takes parameters but not one that takes no parameters.
On the test function I try passing an arguments expected as the letter 'a' in the valid_letter() function. But it gives the error that 'a' is not defined.
def valid_letter():
'''Function valid_letter
Parameters None
continuously asks for a valid letter
if invalid data is provided
'''
while True:
column = input("What column do you wish to select from a to g? ")
if column != "a" and column != "b" and\
column != "c" and column != "d" and\
column != "e" and column != "f" and\
column != "g" and column != "h":
print("Your input is invalid")
continue
else:
return column
break
def check_valid_column(expect):
answer = valid_letter()
print("Input: {}".format(answer))
print("Expected: {}, Actual {}".format(expect, answer))
def main():
check_valid_column(a)
main()
(Note: the original function was broken due to indentation so while I was fixing it I took the liberty of rewriting it to be simpler. It should still behave exactly the same as your intended original implementation and the testing strategy, which is the real focus of the question, is exactly the same regardless of implementation details.)
One way to do this is with patch:
def valid_letter() -> str:
'''
Prompts the user for a column between 'a' and 'g'.
Continuously asks for a valid letter if invalid data is provided.
'''
while True:
column = input("What column do you wish to select from a to g? ")
if ord(column) in range(ord('a'), ord('g') + 1):
return column
print("Your input is invalid")
from unittest.mock import Mock, patch
def test_valid_letter() -> None:
with patch('builtins.input', new=Mock(return_value='a')):
assert valid_letter() == 'a'
with patch('builtins.input', new=Mock(side_effect=['z', 'q', 'g', 'c'])):
assert valid_letter() == 'g'
test_valid_letter()
The patch statements in the test replace the builtin input function with a Mock object that returns a particular argument. In the first test it simply returns 'a', and so we assert that valid_letter() will return that same value. In the second test it returns successive values from the list each time it's called; we assert that valid_letter() will continue calling it in a loop until it reaches 'g'.
Another method would be via dependency injection:
from typing import Callable
from unittest.mock import Mock
def valid_letter(input_func: Callable[[str], str]) -> str:
'''
Prompts the user for a column between 'a' and 'g', using input_func.
Continuously asks for a valid letter if invalid data is provided.
'''
while True:
column = input_func("What column do you wish to select from a to g? ")
if ord(column) in range(ord('a'), ord('g') + 1):
return column
print("Your input is invalid")
def test_valid_letter() -> None:
assert valid_letter(Mock(return_value='a')) == 'a'
assert valid_letter(Mock(side_effect=['z', 'q', 'g', 'c'])) == 'g'
test_valid_letter()
In this example, rather than having valid_letter call the builtin input function, it accepts an arbitrary input function that the caller supplies. If you call it like:
valid_letter(input)
then it behaves exactly like the original, but the caller can also pass in arbitrary replacements without having to use patch. That makes testing a bit easier, and it also allows for the possibility of a caller wrapping or replacing input to allow for a different UI style -- for example, if this function were being used in a GUI app the caller could pass in an input_func that prompts the user via a dialog box instead of the terminal.
The same testing/injection logic applies to the print function -- you might find it useful to have your test validate that print is called each time an invalid value is entered, and another caller might find it useful to use an alternative output function.
The function will work with no parameters, and will still as for user input since you have input() in your called function.
The way you call valid_letter() is fine. That is not the issue. But you intended to verify that the input was "a" (which apparently represents the correct answer).
So then you should not do check_valid_column(a) as a is not a defined variable, but check_valid_column("a"), so with the character "a".
I am quite new to python and probably facing a very simple problem. However, I was not able to find a solution via Google, as the information I found indicated my method should work.
All I want to do is passing in an array as argument to a function.
The function that shall take an array:
def load(components):
global status
global numberOfLoadedPremixables
results = []
print('componetnsJson: ', componentsJson, file=sys.stderr)
status = statusConstants.LOADING
for x in range(0, len(components)):
blink(3)
gramm = components[x]['Gramm']
#outcome = load(gramm)
time.sleep(10)
outcome = 3
results.append(outcome)
numberOfLoadedPremixables += 1
status = statusConstants.LOADING_FINISHED
Then I am trying to start this function on a background thread:
background_thread = threading.Thread(target=load, args=[1,2,3]) #[1,2,3] only for testing
background_thread.start()
As a result, I end up with the error:
TypeError: load() takes 1 positional argument but 3 were given
Since you need to pass the whole array as a single unit to the function, wrap that in a tuple:
background_thread = threading.Thread(target=load, args=([1,2,3],))
The (,) turns the args into a single-element tuple that gets passed to your function
The issue is happening because python expects args to be a sequence which gets unwrapped when being passed to the function, so your function was actually being called like: load(1, 2, 3)
I'm a beginner with python and I'm facing a problem with a function that requires optional parameters.
This function gets as parameters a variable number of file paths, that can be from 2 to n parameters.
After that, a certain number of optional parameters can be passed to this function.
I tried to do something like that:
def compareNfilesParameters(*args):
start_time = time.time()
listFiles = []
listParameters = []
for argument in args:
if str(argument).endswith(".vcf"):
listFiles.append(str(argument))
else:
listParameters.append(argument)
So if the parameters has the file extension it is considered as one of the file path parameters, the others are seen as the optional parameters.
What I want to do is letting the user call the function like:
function('a.vcf', 'b.vcf', 'c.vcf')
or
function('a.vcf', 'b.vcf', 'c.vcf', 0, 1)
or
function('a.vcf', 'b.vcf', 'c.vcf', 0, 1, 4,...,3)
I tried different approaches but none of them satisfies me.
The first approach is declaring the function as:
def compareNfilesParameters(*args)
but this way, if I get for example 3 parameters, 2 will certainly be the files path, and the last one I don't know on which variable it refers. So I need to specify every value and pass '-1' for the parameters that I want to use default value.
The 2nd approach is the following:
def compareNfilesParameters(*args, par1 = 10, par2 = 15 ..)
But this way I need to call the function like:
compareNfilesParameters(path1, path2, path3, par1 = 10)
and not like
compareNfilesParameters(path1, path2, path3, 10)
or the 10 will be considered in the args input, right? I wouldn't like to use this approach because it becomes very verbose to call the function.
How would you do this?
Make the user pass in the filenames as a sequence; don't try to cram everything into separate arguments:
def compareNfilesParameters(files, *params):
and call this as:
compareNfilesParameters(('a.vcf', 'b.vcf', 'c.vcf'), 0, 1, 4)
This makes the files explicit and removes the need to separate files from other parameters.
If your remaining parameters are distinct options (and not a homogenous series of integers), I'd use keyword arguments:
def compareNfilesParameters(files, op1=default_value, op2=default_value, op3=default_value):
You don't have to use keyword arguments with keywords when calling; you can still treat them as positional:
compareNfilesParameters(('a.vcf', 'b.vcf', 'c.vcf'), 0, 1, 4)
would give op1 the value 0, op2 the value 1, and op3 the value 4. Only if you want to specify values out of order or for a specific option do you have to use keyword arguments in the call:
compareNfilesParameters(('a.vcf', 'b.vcf', 'c.vcf'), op3=4)
Ok, I solved like using the keyword parameters as suggested.
def compareNfilesParameters(listFiles, **kwargs):
start_time = time.time()
if len(listFiles) < MINUMUM_FILES_NUMBER :
print "You need to specify at least "+ str(MINUMUM_FILES_NUMBER) +" files."
return
try:
operationType = int(kwargs.get("op", DEFAULT_OPERATION_TYPE))
except ValueError:
print "Operation type filter has to be an integer."
return
if operationType not in [0,1]:
print "Operation type must be 0 (intersection), 1 (union)"
return
and so on for all the parameters.
Like this I need to put all the files paths in a list and pass it as a single required parameter, and searching kwargs dictionary for optionals parameters setting the default values if not expressed.