Character Creation in Text Adventure; Proper use of Main and Global Values? I'm still learning python so I decided to experiment by making a tiny text adventure. I'm only a student in highschool so I haven't had the chance to learn many common techniques used in code as most of my learning has been self taught, so I wanted to take this opportunity to have some kind of review of some of the techniques I use in this code.

Here it is in it's entirety:

class Character:
    def __init__(self, class_name, strength, dexterity, intelligence):
        self.class_name = class_name
        self.strength = strength
        self.dexterity = dexterity
        self.intelligence = intelligence
        self.health = strength*10

    def print_stats(self):
        return self.class_name + " Strength: " + str(self.strength) + " Dexterity: " + str(self.dexterity) + " Intelligence: " + str(self.intelligence)

    def print_stat_strength(self):
        return "Strength: " + str(self.strength)

    def print_stat_dexterity(self):
        return "Dexterity: " + str(self.strength)

    def print_stat_intelligence(self):
        return "Intelligence: " + str(self.strength)

    def print_stat_health(self):
        return "Max Health: " + str(self.health)

    def change_stat_strength(self, new_value):
        self.strength = new_value

    def change_stat_dexterity(self, new_value):
        self.dexterity = new_value

    def change_stat_intelligence(self, new_value):
        self.intelligence = new_value

def character_select():
    global player_class
    barbarian = Character("Barbarian", 20, 10, 10)
    archer = Character("Archer", 10, 20, 10)
    wizard = Character("Wizard", 10, 10, 20)

    print("Welcome to TEST TEXT ADVENTURE RPG. Choose a character!")
    print("(A): " + barbarian.print_stats())
    print("(B): " + archer.print_stats())
    print("(C): " + wizard.print_stats())

    while True:
        user_answer = input().upper()
        if user_answer == "A":
            player_class = barbarian
            break
        elif user_answer == "B":
            player_class = archer
            break
        elif user_answer == "C":
            player_class = wizard
            break
        else:
            print("Invalid input! Try again.")
    print("####CHARACTER CREATION COMPLETE####")


def main():
    character_select()
    print(player_class.print_stats())

main()


Looking at this, my main concern is how the main function is utilized. To my knowledge it is supposed to be relatively clean and made up of other functions, lacking a lot of original code of its own. Making the above, I came into an issue trying to use the player_class object that I created, since it was created in the character_select function, it could only be used there unless it was global. The global fix seems kind of clunky so I was wondering the proper way to implement this kind of thing.

One other concern that I can identity could be the built in functions in the Character class. Do I need to make a separate function for every single stat like strength, dexterity, intelligence, or is it possible to do something like this?

def change_stat(self, stat_to_change, new_number):
    self.stat_to_change = new_number


Now I'm asking this question because I tried the above code on my own to try to cut down on the amount of similar functions for each individual stat, but I couldn't seem to use it how I imagined. 

Those are my main concerns but anybody reading this is welcome to bring up any other improper coding technique. I really don't want bad habits to manifest themselves and continue to appear if I can help it.
There is a way to pass the attribute name which you want to change, you can use setattr:

def change_stat(self, stat_to_change, new_number):
    setattr(self, stat_to_change, new_number)


However, there is no difference to what would happen if you just say self.strength = 10 instead of self.change_stat("strength", 10).

Regarding the need for global for the player character, there isn't. Just return the chosen class from the character_select function:

def character_select():
    barbarian = Character("Barbarian", 20, 10, 10)
    archer = Character("Archer", 10, 20, 10)
    wizard = Character("Wizard", 10, 10, 20)

    print("Welcome to TEST TEXT ADVENTURE RPG. Choose a character!")
    print("(A): " + barbarian.print_stats())
    print("(B): " + archer.print_stats())
    print("(C): " + wizard.print_stats())

    while True:
        user_answer = input().upper()
        if user_answer == "A":
            return barbarian
        elif user_answer == "B":
            return archer
        elif user_answer == "C":
            return wizard
        else:
            print("Invalid input! Try again.")


You could make it even nicer if you used a dictionary:

from collections import OrderedDict

def character_select():
    classes = OrderedDict([("A", Character("Barbarian", 20, 10, 10)),
                           ("B", Character("Archer", 10, 20, 10)),
                           ("C", Character("Wizard", 10, 10, 20))])

    print("Welcome to TEST TEXT ADVENTURE RPG. Choose a character!")
    for key, cls in classes.items():
        print("({}): {}".format(key, cls.print_stats()))

    while True:
        user_answer = input().upper()
        try:
            return classes[user_answer]
        except KeyError:
            print("Invalid input! Try again.")


You can then use it like this in your main function:

def main():
    player_class = character_select()
    print(player_class.print_stats())


You should probably rename your Character.print_stats function. Right now it sounds like it would actually print the stats, but it just returns them. So I would make your class like this:

class Character:
    def __init__(self, class_name, strength, dexterity, intelligence):
        self.class_name = class_name
        self.strength = strength
        self.dexterity = dexterity
        self.intelligence = intelligence
        self.health = strength*10

    def __str__(self):
        return "{self.class_name} " \
               "Health: {self.health} " \
               "Strength: {self.strength} " \
               "Dexterity: {self.dexterity} " \
               "Intelligence: {self.intelligence}".format(self=self)


If you are using Python 3.6, you can use f"{self.class_name} ..." instead of "{self.class_name} ...".format(self=self), which uses f-strings.

Because I named the function __str__, you can now do:

barbarian = Character("Barbarian", 20, 10, 10)
print(barbarian)
# Barbarian Health: 200 Strength: 20 Dexterity: 10 Intelligence: 10


And the loop in character_select becomes this, because str.format automatically calls str on its arguments, which will then invoke the Character.__str__ function:

for key, cls in classes.items():
    print("({}): {}".format(key, cls))


__str__ is one of the magic (or dunder) methods, which can give your class features so it acts like built-in classes. Have a look at this nice guide for a list and explanation of all of them.

Finally, you should add a if __name__ == "__main__": guard to allow importing your functions from other scripts:

if __name__ == "__main__":
    main()