Home > Software engineering >  Solving a Positional Argument Error in a Pythonic Class and Method
Solving a Positional Argument Error in a Pythonic Class and Method

Time:02-11

Disclaimer To preface this, I am new to programming and even newer to python, so my knowledge of the mechanics of the interpreter is very limited.

Explanation I am currently writing a pythonic code that simulates a text-based game of cheating hangman, which means the program changes the word to evade the player from guessing the correct word using "word families." The game requires three files: hangman.py, play_hangman.py, and dictionary.txt. In hangman.py, I have created a hangman class that contains the self-instance of the class, several methods for producing the necessary objects, and a play method that uses these methods. Then, in play_hangman.py, it calls the hangman instance, and the play method, and puts the play method into a while loop that repeats as long as the player enters "yes" to keep playing.

The Problem I have called the methods into the play function for executing the game. However, it gives me an error saying:

- "12 positional arguments: 'word_length', 'num_guesses', 'remaining_words', 'remainingWords', 'letters_already_guessed', 'askForWordLength', 'printGameStats', 'askForPlayerGuess', 'wordStatus', 'printCountOfRemainingWords', 'retreiveRemainingWords', and 'askForNumberOfGuesses' "

These are the twelve variables and methods I have called in the def play(): function. I have researched that I need to call an object of the class before the method, which I attempted to do, but does not work. I am not sure how to avoid the error.

HANGMAN.PY

import re


class Hangman:

    # hangman self method
    def hangman(self):
        self.hangman = Hangman()  # object of the Hangman class

    def words(self):
        with open('dictionary.txt') as file:  # opens dictionary text file
            file_lines = file.read().splitlines()  # reads and splits each line

        all_words = []  # empty list to contain all words
        valid_words = []  # empty list to contain all valid words

        for word in file_lines:  # traverses all words in the file lines
            if len(word) >= 3:  # accepts word if it has at least 3 letters
                all_words.append(word)  # appends accepted word to list

        # list of all invalid characters in python
        CHARACTERS = ["~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(",
                      ")", "-", "_", "=", " ", "[", "]", "{", "}", "|", "\","
                      "", "'", "?", "/", ">", ".", "<", ",", "", ";", ":"]

        for i in CHARACTERS:    # traverse list of invalids
            for word in all_words:
                if i not in word:   # if invalid character is not in word
                    valid_words.append(word)    # accept and append to list

        return valid_words  # return list of valid words

    def askForWordLength(self, valid_words):

        word_lengths = []   # empty list for possible word lengths
        for word in valid_words:    # traverse list of valid words
            length = word.__len__()  # record length of current word
            if (length not in word_lengths):
                word_lengths.append(length)     # accept and append to list
        word_lengths.sort()

        # inform user of possible word lengths
        print('The available word lengths are: '   str(word_lengths[0])   '-'
                str(word_lengths[-1]))
        print()

        # have user choose from possible word lengths
        while(1):
            try:
                length = int(input('Please enter the word length you want: '))
                if (length in word_lengths):
                    return length
            except ValueError:
                print('Your input is invalid!. Please use a valid input!')
                print()

    def askForNumberOfGuesses(self):
        while(1):
            try:
                num_guesses = int(input('Enter number of guesses you want: '))
                if (num_guesses >= 3):
                    return num_guesses
            except ValueError:
                print('Your input is invalid!. Please use a valid input!')
                print()

    def wordStatus(self, length):
        status = '-'
        for i in range(0, length):
            status  = '-'
        return

    def remainingWords(self, lines, length):
        words = []
        for word in lines:
            if (word.__len__() == length):
                words.append(word)
        return words

    def printGameStats(self, letters_guessed, status, num_guesses):
        print('Game Status: '   str(status))
        print()
        print('Attempted Guesses'   str(letters_guessed))
        print('Remaining Guesses'   str(num_guesses))

    def askPlayerForGuess(self, letters_guessed):
        letter = str(input('Guess a letter: ')).lower()
        pattern = re.compile("^[a-z]{1}$")
        invalid_guess = letter in letters_guessed or re.match(pattern, letter) == None

        if (invalid_guess):
            while (1):
                print()
                if (re.match(pattern, letter) == None):
                    print('Invalid guess. Please enter a correct character!')
                if (letter in letters_guessed):
                    print('\nYou already guessed that letter'   letter)

                letter = str(input('Please guess a letter: '))
                valid_guess = letter not in letters_guessed and re.match(pattern, letter) != None

                if (valid_guess):
                    return letter
        return letter

    def retrieveWordStatus(self, word_family, letters_already_guessed):
        status = ''
        for letter in word_family:
            if (letter in letters_already_guessed):
                status  = letter
            else:
                status  = '-'
        return status

    def retrieveRemainingWords(self, guess, num_guesses, remaining_words,
                               wordStatus, guesses_num, word_length,
                               createWordFamiliesDict,
                               findHighestCountWordFamily,
                               generateListOfWords):

        word_families = createWordFamiliesDict(remaining_words, guess)
        family_return = wordStatus(word_length)
        avoid_guess = num_guesses == 0 and family_return in word_families

        if (avoid_guess):
            family_return = wordStatus(word_length)
        else:
            family_return = findHighestCountWordFamily(word_families)

        words = generateListOfWords(remaining_words, guess, family_return)
        return words

    def createWordFamiliesDict(self, remainingWords, guess):
        wordFamilies = dict()
        for word in remainingWords:
            status = ''
            for letter in word:
                if (letter == guess):
                    status  = guess
                else:
                    status  = '-'

            if (status not in wordFamilies):
                wordFamilies[status] = 1
            else:
                wordFamilies[status] = wordFamilies[status]   1
        return wordFamilies

    def generateListOfWords(self, remainingWords, guess, familyToReturn):
        words = []
        for word in remainingWords:
            word_family = ''
            for letter in word:
                if (letter == guess):
                    word_family  = guess
                else:
                    word_family  = '-'

            if (word_family == familyToReturn):
                words.append(word)
        return words

    def findHighestCountWordFamily(self, wordFamilies):
        familyToReturn = ''
        maxCount = 0
        for word_family in wordFamilies:
            if wordFamilies[word_family] > maxCount:
                maxCount = wordFamilies[word_family]
                familyToReturn = word_family
        return familyToReturn

    def printCountOfRemainingWords(self, remainingWords):
        show_remain_words = str(input('Want to view the remaining words?: '))
        if (show_remain_words == 'yes'):
            print('Remaining words: '   str(len(remainingWords)))
        else:
            print()

    def play(self, askForWordLength, askForNumberOfGuesses, remainingWords,
             words, wordStatus, printCountOfRemainingWords, printGameStats,
             askPlayerForGuess, retrieveRemainingWords):
        MODE = 1
        openSession = 1

        while (openSession == 1):
            word_length = askForWordLength(words)
            num_guesses = askForNumberOfGuesses()

            wordStatus = wordStatus(word_length)
            letters_already_guessed = []
            print()

            game_over = 0
            while (game_over == 0):
                if (MODE == 1):
                    printCountOfRemainingWords(remainingWords)

            printGameStats(remainingWords, letters_already_guessed,
                           num_guesses, wordStatus)

            guess = askPlayerForGuess(letters_already_guessed)
            letters_already_guessed.append(guess)
            num_guesses -= 1

            remainingWords = retrieveRemainingWords(guess, remainingWords,
                                                    num_guesses, word_length)

            wordStatus = wordStatus(remainingWords[0], letters_already_guessed)
            print()

            if (guess in wordStatus):
                num_guesses  = 1

            if ('-' not in wordStatus):
                game_over = 1
                print('Congratulations! You won!')
                print('Your word was: '   wordStatus)

            if (num_guesses == 0 and game_over == 0):
                game_over = 1
                print('Haha! You Lose')
                print('Your word was: '   remainingWords[0])

        print('Thanks for playing Hangman!')

CodePudding user response:

Self as First Parameter

When creating functions inside classes, the first parameter should be 'self', as you've done in the function askForNumberOfGuesses. This happens because, when calling functions from an object, Python passes the object to 'self' so the method's logic can access its data.

When calling functions such as "play" (in which you haven't used the self parameter in the declaration), the first parameter will be handled as the self, even though it has a different name. So, actually, your code expects the variable 'words' to be a Hangman object.

Static Functions

If the function needs to know information about the object, you need to add the 'self' as the first parameter. However, if your method does not use any data declared inside the Hangman class (such as self.hangman that you've created inside init) you can just add a "@staticmethod" to the line before the function definition. This way, Python will know that you don't need self and that the first parameter is not the object.

Notice that, if you want to use a static method, you can use the class itself to call the method. You don't even need to create an object:

Hangman.static_function_name(function_parameters)  # calls static function
hangman_object = Hangman()
hangman_object.non_static_function_name(function_parameters)  # calls non-static function
hangman_object.static_function_name(function_parameters)  # calls static function using object (there is no problem)

Design Considerations

Also, I believe you should change your init implementation and use 'self' to store data that you use in more than one function, instead of passing arguments to all the functions and returning them. Also note that, in order to create a Hangman object, you need to use ():

self.hangman = Hangman  # reference to the Hangman class
self.hangman = Hangman()  # Object of the Hangman class

However, if you create a Hangman object inside init, it will call init again and this will go on "forever". So, you shouldn't create a Hangman object inside it's init method. I believe you should create the Hangman object in play_hangman.py file and call the play method from there.

In my opinion, init should create all the data you need as self.<var_name>, load should prepare words or whatever you need using self.<functions_you_create> and play should just start the game. play_hangman.py shouldn't need to know all these parameters.

hangman.py

class GameClass:
    def __init__(self):
        self.words = list()
        # TODO: Implement adding other game data

    def load(self):
        # TODO: Implement logic
        self.words.append('country')
        pass

    def play(self):
        # TODO: Implement loop logic
        print(self.words)
        # while(True):
        #    break
        pass

play_hangman.py

game = GameClass()
game.load()
game.play()

IDE

Use a good IDE so it will remember you to add self if you are not used yet.

CodePudding user response:

None of those methods have self as their first argument. And that is what is used to define class methods and give access to attributes. So, adding that "self" keyword is the first step to make your code work.

I would recommend installing a linter, it automatically identifies this kind of error for you: enter image description here

  • Related