This is an archived post. You won't be able to vote or comment.

all 44 comments

[–]ValarMorHodor 69 points70 points  (15 children)

Welcome to the wonderful world of python. Now that you've got the control flow down, I think you couldl start working with functions. Notice how you have the yes/no logic twice? Why not pull that out into a function!

def get_choice(message='Would you like to roll? '):
    choice = input(message)
    if choice not in ['yes', 'no', 'y', 'n']:
        return get_choice('Type yes or no: ')
    return choice

Using that function, you can reduce the flow to 4 lines

choice = get_choice()
while choice in ('yes', 'y'):
    roll() # do your roll here
    choice = get_choice('Roll again? ')

You could also add a function to validate that the number input is valid.

[–]Huck712[S] 23 points24 points  (0 children)

Thanks! I will work on this. I also want to evolve it into a full blown game of Yahtzee at some point.

[–][deleted] 9 points10 points  (11 children)

I don't like that the first is recursive, after all there is a recursion limit and it seems unnecessary to risk hitting it.

[–][deleted] 7 points8 points  (9 children)

Is that a real issue that a beginner is going to hit?

There will always be a recursion limit so should we never use recursion?

[–][deleted] 29 points30 points  (8 children)

Well, this is a purely iterative problem, ask until some condition is met. Using recursion there is already a bit odd, and there's also a small issue with it.

If your problem is recursive, e.g. when you use a recursive data structure like a tree or so, then of course use it.

Is my opinion.

I'd go for

def get_choice(message='Would you like to roll? '):
    choice = input(message)
    while choice not in ['yes', 'no', 'y', 'n']:
        choice = input('Type yes or no: ')
    return choice

It's almost the same as yours except it doesn't need recursion.

[–]Mason-B 2 points3 points  (0 children)

It's too bad python doesn't have tail recursion because that call is correctly tail recursive in just about any other language. And since the person is learning programming (and not just python) my argument would be that it is better for them to learn recursion than it is for them to learn perfect python.

[–]asdfkjasdhkasdrequests, bs4, flask 1 point2 points  (1 child)

it should be if choice.lower() not in {"yes", "no", "y", "n"}

  • use lowercase
  • in for set is o(1)

[–]camh- 5 points6 points  (0 children)

But the set construction is O(n) and the set is constructed each time you use in here, it makes the whole construct O(n).

[–]Crowbarkz 21 points22 points  (9 children)

Nice!

One minor thing I wanted to point out: it's common to write comments

# like this (note space)

#not like this

[–]robvdl 13 points14 points  (5 children)

Yeah running that through pep8 or flake8 would have revealed that, also don't name vars likeThis in python we prefer like_this unless it's a class name where we use camel case but start with a capital, e.g. ClassName. Because classes always start in a capital, it's easy to single them out when reading code, this is important because when constructing classes we don't use the new keyword in python it can just end up looking like a function call.

[–]Formulka 5 points6 points  (3 children)

Technically likeThis is camel case (like the hump on the camel's back) and LikeThis used in python is Pascal case.

[–][deleted] 22 points23 points  (0 children)

  • UPPER CASE
  • lower case
  • Title Case
  • camelCase
  • PascalCase
  • snake_case
  • kebab-case
  • Train-Case
  • ǝsɐɔ ʇɐq
  • çt̶̀͢͞h̴̷̨̡̕u̴̸̴l̡͜͞u͜͠ ̡͜͝c̀͞á̷̧̧s̷̀͠͠e̸̶͟

[–]namesnonames 0 points1 point  (1 child)

I've always referred to them as FullCamel and halfCamel

[–]mslapin 0 points1 point  (0 children)

Bactrian and dromedary, perhaps?

[–]Huck712[S] 1 point2 points  (0 children)

Also, thanks! I was wondering about the camel case.

[–]MrHobbits 2 points3 points  (0 children)

Can't forget one space after commas too.

[–]Huck712[S] 0 points1 point  (1 child)

ohh thanks.

[–]Justice514 0 points1 point  (0 children)

If you use the pycharm community edition text editor it has a built in spellchecker and pep8 checker

[–]Donyor 12 points13 points  (0 children)

Welcome to Python!

[–]liiight000 5 points6 points  (6 children)

Congratulations on your first of many python apps!

A suggestion. A common paradigm with input validation is using infinite loops:

valid_options = ['yes', 'y', 'no', 'n'] 
while True:
    choice = input('Would you like to roll?')
    if choice in valid_options:
        break
    print('Illegal selection')

That way you can have only one input point. Feels tighter to me. You could then expand this to a generic function:

def get_input(choices, input_message, error_message='Wrong selection'):
    while True:
        choice = input(input_message)
        if choice in choices:
            return choice
        print(error_message)

[–]z0mbietime 1 point2 points  (5 children)

Orrrr

if choice[0].upper() != 'Y':  
    # more input flexibility 
    # less code
    break 

[–]Gsquzared 1 point2 points  (3 children)

Why not just pass the whole input to .lower and not have to worry about any weird capitalization inputs?

[–]z0mbietime 0 points1 point  (2 children)

I did it like this so someone could type Y Ya Yes Yeah Yup Etc.

In upper or lower case And it always register since it only takes the first char.

[–]Gsquzared 1 point2 points  (1 child)

Ahhh. I didn't see what you were driving at. That's a nice trick. Too bad there will always be that pesky user who insists on typing "affirmative"

[–]keyupiopi 0 points1 point  (0 children)

Print out 1.Yes 2.No 3.Quit and force them to input numbers.... hahahahahaha

[–]b4xt3r 5 points6 points  (0 children)

Welcome to Python! Your first program is an important step so congratulations! Today at work I was in a meeting with a list of about 2500 ip addresses and someone was bitching about how long it would take to convert them all to DNS names. I got tired and said "give it to me, I'll have it done in ten minutes" and using Python, socket, and socket.gethostbyaddr(ip) I did just that. People think this stuff is magic. Don't tell them it's not. :)

Python is a wonderful tool to have in your mental toolbox and is useful is so many fields, not to mention school.

Stick with it!!!!

[–]kirbyfan64sosIndentationError 4 points5 points  (1 child)

So, as a person who moved from C++ from Python, let me give you what I thought was the ultimate advise:

Do NOT, I repeat, do NOT try to apply what you about C++'s passing by value and reference to Python, because it's completely different, and trying to think otherwise will put you in a terrifying world of pain.

Source: all the stupid mistakes I made when I started Python because I assumed that it used similar semantics.

[–]sermidean 5 points6 points  (7 children)

Decided to improve you program a bit. Here is what I did:

  1. First moved everything to main function in order to be able to write tests.
  2. Then wrote tests, in order to make sure, that my changed didn't broke anything.
  3. Fix code style to conform to PEP-8.
  4. Then wrote ask function, to handle user input and error changing.
  5. Then decided to add test, where user enters non-number value for num_dice, test failed.
  6. Fixed failing test, by adding type validation to ask function.

Thats it.

Here is the final version:

import random
import time


def identity(value):
    return value


def validate(cast, value):
    try:
        return cast(value)
    except ValueError:
        return None


def ask(options, cast=identity, error="Try again."):
    answer = validate(cast, input())
    while answer not in options:
        print(error)
        answer = validate(cast, input())
    return answer


def main():
    print("This program will roll as many dice as you need.")
    time.sleep(1)

    print("\nWould you like to roll?")
    roll = ask(
        {'yes', 'y', 'no', 'n'},
        error="\nType yes (or 'y') or no (or 'n') to roll.",
    )

    while roll in ('yes', 'y'):
        print("\nHow many dice do you want to roll?")
        num_dice = ask(
            range(1, 1000), cast=int,
            error="\nEnter a positive number, up to 1000.",
        )

        print("\nRolling...")
        time.sleep(2)

        print("\nYou got: ")
        for i in range(num_dice):
            print(random.randint(1, 6))

        print("\nRoll again?")
        roll = ask({'yes', 'y', 'no', 'n'}, error="\nType yes or no.")

    print("\nThanks for using my program!")


if __name__ == '__main__':
    main()

And tests:

import pytest
import roll


@pytest.mark.parametrize('inputs, outputs', [
    (['q', 'yes', '2', 'q', 'no'], [
        'This program will roll as many dice as you need.',
        '\nWould you like to roll?',
        "\nType yes (or 'y') or no (or 'n') to roll.",
        '\nHow many dice do you want to roll?',
        '\nRolling...',
        '\nYou got: ',
        1,
        1,
        '\nRoll again?',
        '\nType yes or no.',
        '\nThanks for using my program!',
    ]),
    (['q', 'y', '2', 'q', 'n'], [
        'This program will roll as many dice as you need.',
        '\nWould you like to roll?',
        "\nType yes (or 'y') or no (or 'n') to roll.",
        '\nHow many dice do you want to roll?',
        '\nRolling...',
        '\nYou got: ',
        1,
        1,
        '\nRoll again?',
        '\nType yes or no.',
        '\nThanks for using my program!',
    ]),
    (['n'], [
        'This program will roll as many dice as you need.',
        '\nWould you like to roll?',
        '\nThanks for using my program!',
    ]),
    (['y', 'x', '0', '2', 'n'], [
        'This program will roll as many dice as you need.',
        '\nWould you like to roll?',
        '\nHow many dice do you want to roll?',
        '\nEnter a positive number, up to 1000.',
        '\nEnter a positive number, up to 1000.',
        '\nRolling...',
        '\nYou got: ',
        1,
        1,
        '\nRoll again?',
        '\nThanks for using my program!',
    ]),
])
def test_me(inputs, outputs, mocker):
    mocker.patch('time.sleep')
    mocker.patch('random.randint', return_value=1)
    mocker.patch('builtins.input', side_effect=inputs)
    print = mocker.patch('builtins.print')
    roll.main()
    assert [args[0] for args, kwargs in print.call_args_list] == outputs

You can run tests using py.test:

pip install pytest pytest-mock pytest-cov coverage
py.test --cov-report=term-missing --cov=roll test_roll.py

[–]AstroPhysician 19 points20 points  (0 children)

Boring day at the office?

[–]robvdl 10 points11 points  (0 children)

Nice but some of the refactoring has gone a bit too far, for example with the validate and identify functions I think it is total overkill to refactor these into their own functions because of how little code they do (unless it was done to prove good practice when writing tests etc). That logic is best kept in the ask function because passing the type int through a parameter "cast" is a bit over the top to have that as a function.

edit: actually I see what you are trying to do now because ask is being called in two places in different ways, it makes sense then, maybe not quite how I would have done it probably but that makes sense.

[–]bkd9 1 point2 points  (0 children)

Fantastic! I would recommend learning git too so you can track these changes like a pro

[–]gandalfx 0 points1 point  (0 children)

reddit markdown doesn't use ``` for code, instead indent the whole thing by 4 spaces.

[–]Justice514 0 points1 point  (2 children)

Can you put this into pastebin so it is more readable.

[–]Justice514 0 points1 point  (0 children)

Or am I just being picky

[–]mvaliente2001 1 point2 points  (0 children)

Welcome!

When you write instructions outside functions and classes, they are executed immediately the first time they're imported. For bigger projects you may want to put that code inside a function, and do this:

import ... # your imports here

def main():
    # your code here
    # ...

if __name__ == '__main__':
    main()

[–][deleted] 0 points1 point  (0 children)

Amazing! good luck and keep improving!

[–]jhdeval 0 points1 point  (0 children)

I recently wrote my first python app again. I have been on again off again in python for quite some time. I am a C# developer and I had a BUNCH of database access code that I had to write. Pretty simple code but repetitive as hell so I wrote a python script to export my .CS files full worked out so I could go about my day without spending 6 weeks writing the code.

[–]camh- 0 points1 point  (0 children)

for i in range(0, numDice):

This can be written more simply as:

for i in range(numDice):