The main problem you're having right now is two-fold - one, that the replace() method replaces all instances of any given input within a string, not the first one, and two, that you don't currently have any way of telling which letters you've already uncovered. Calling replace("_", let) will always replace every single instance of "_", and given that you're applying that to a string that is only composed of underscores, it'll always overwrite the entire string. It seems like you're also regenerating hidden_let every time hangman() is called with a guess letter, meaning that best-case with your design now you're only going to ever show every letter the user just guessed and a bunch of underscores otherwise.
What you'd want to do is have two values, correct_word and current_guess. correct_word will be the word the player has to guess, and current_guess will be their progress in guessing the word, starting with a string of only underscores of the same length as correct_word.
Here's a short example. I've taken the liberty of removing your global references - globals are generally frowned upon - and encapsulated the behavior in a small class. You'd want to replace the value in hangmanner.play_hangman() with whatever your random word is.
class Hangmanner:
correct_word = ''
current_guess = ''
def play_hangman(self, word):
self.correct_word = word
self.current_guess = '_' * len(self.correct_word)
while self.current_guess != self.correct_word:
self.guess_letter(input("Please guess a letter: "))
def guess_letter(self, guessed_letter):
for i in range(len(self.correct_word)):
if self.correct_word[i] == guessed_letter:
if i == 0:
self.current_guess = self.correct_word[i] + self.current_guess[1:]
else:
self.current_guess = self.current_guess[:i] + self.correct_word[i] + self.current_guess[i + 1:]
print(self.current_guess)
if __name__ == "__main__":
hangmanner = Hangmanner()
hangmanner.play_hangman("test")
This uses the slicing function in python, where you can use the brackets and the [first:last] syntax to access an arbitrary range of any given collection. If either first or last is missing, the slice continues to the beginning or end of the collection, respectively. Above, current_guess[1:] returns current_guess from the second index to the last. current_guess[:i] returns current_guess from the first index up to the index preceding i, given that last is the exclusive end bound.