5
\$\begingroup\$

My goal was to create a "calculator" to compute the monthly property management fee for two vacation rental condos my wife and I own.

My purpose was to take user input for name, dates and rental rate and compute the total property management fee for the two properties. It took me a little research and trial and error to get the arrival and departure dates to compute the total number of nights for each guest's stay, but I worked it out and everything works well and passed all the tests I could throw at it.

Basically, here are the steps:

  1. Input guest's name, arrival/ departure dates, rental rate and compute total rental fee for this guest
  2. If there are more guests, do the same for those, if not, print out the total rental fee for the first property and move onto the next
  3. Perform the same functions as the first property
  4. When there are no other guests to enter, compute total sum of rental fees from the two properties and multiply by 10% to get the total property management fee for the month

As a beginner, I was super excited when I finished and all my tests passed! However, as a beginner, I'm sure there's some things I can do better and would highly appreciate any critique you would like to offer.

import datetime
any_more_ps_guests = "yes"
total_ps_rental_fee = 0


while any_more_ps_guests == "yes":

    ps_guest_name = input('Pacific Shores Guest Name:  ')

    ps_arrival_date_input = input("Arrival Date (xx/xx/xxxx):  ")
    ps_arrival_date_month = int(ps_arrival_date_input[:2])
    ps_arrival_date_day = int(ps_arrival_date_input[3:5])
    ps_arrival_date_year = int(ps_arrival_date_input[6:])
    ps_arrival_date = ps_arrival_date_month, ps_arrival_date_day, ps_arrival_date_year

    ps_departure_date_input = input("Departure Date (xx/xx/xxxx):  ")
    ps_departure_date_month = int(ps_departure_date_input[:2])
    ps_departure_date_day = int(ps_departure_date_input[3:5])
    ps_departure_date_year = int(ps_departure_date_input[6:])
    ps_departure_date = ps_departure_date_year, ps_departure_date_month, ps_departure_date_day

    def number_of_nights():
        date_one = datetime.date(ps_arrival_date_year, ps_arrival_date_month, ps_arrival_date_day)
        date_two = datetime.date(ps_departure_date_year, ps_departure_date_month, ps_departure_date_day)
        return (date_two - date_one).days


    def ps_nightly_rate():
        rate = input('Rate:  ')
        return rate


    ps_total_nights = number_of_nights()
    ps_rate = ps_nightly_rate()
    ps_total_rental_fee = int(ps_total_nights) * int(ps_rate)

    print([ps_guest_name, ps_total_nights, ps_rate, ps_total_rental_fee])

    total_ps_rental_fee += ps_total_rental_fee

    anyone_else = input('Are there anymore guests?  ')
    if anyone_else == 'no':
        break


print('Pacific Shores Total Rental Fee:  ', total_ps_rental_fee)

any_more_kkn_guests = 'yes'
total_kkn_rental_fee = 0

while any_more_kkn_guests == "yes":

    kkn_guest_name = input('Kihei Kai Nani Guest Name:  ')

    kkn_arrival_date_input = input("Arrival Date (xx/xx/xxxx):  ")
    kkn_arrival_date_month = int(ps_arrival_date_input[:2])
    kkn_arrival_date_day = int(ps_arrival_date_input[3:5])
    kkn_arrival_date_year = int(ps_arrival_date_input[6:])
    kkn_arrival_date = kkn_arrival_date_month, kkn_arrival_date_day, kkn_arrival_date_year

    kkn_departure_date_input = input("Departure Date (xx/xx/xxxx):  ")
    kkn_departure_date_month = int(ps_departure_date_input[:2])
    kkn_departure_date_day = int(ps_departure_date_input[3:5])
    kkn_departure_date_year = int(ps_departure_date_input[6:])
    kkn_departure_date = kkn_departure_date_year, kkn_departure_date_month, kkn_departure_date_day


    def number_of_nights():
        date_one = datetime.date(kkn_arrival_date_year, kkn_arrival_date_month, kkn_arrival_date_day)
        date_two = datetime.date(kkn_departure_date_year, kkn_departure_date_month, kkn_departure_date_day)
        return (date_two - date_one).days

    def kkn_nightly_rate():
        rate = input('Rate:  ')
        return rate



    kkn_total_nights = number_of_nights()
    kkn_rate = kkn_nightly_rate()
    kkn_total_rental_fee = int(kkn_total_nights) * int(kkn_rate)

    print([kkn_guest_name, kkn_total_nights, kkn_rate, kkn_total_rental_fee])

    total_kkn_rental_fee += kkn_total_rental_fee

    anyone_else = input('Are there anymore guests?  ')
    if anyone_else == 'no':
        break

print('Kihei Kai Nani Total Rental Fee:  ', total_kkn_rental_fee)

print('Total Rental Fee Amount: ', total_ps_rental_fee + total_kkn_rental_fee)

total_management_fee = (total_ps_rental_fee + total_kkn_rental_fee) * 0.10

print('Total Management Fee:  ', '{0:.2f}'.format(total_management_fee))
\$\endgroup\$
4
\$\begingroup\$

First of, welcome to Python's World and congrats for your first project!

Style (PEP8) / Readability

Python has this PEP8 style guide which gives coding conventions for the Python code. In most of your code you did fine, but there's room for improvement when it comes to readability.

At some point, you had too many blank lines (3) which is a bit too much. Make it one single newline:

def kkn_nightly_rate():
    rate = input('Rate:  ')
    return rate



kkn_total_nights = number_of_nights()
kkn_rate = kkn_nightly_rate()
kkn_total_rental_fee = int(kkn_total_nights) * int(kkn_rate)

Should be:

def kkn_nightly_rate():
    rate = input('Rate:  ')
    return rate

kkn_total_nights = number_of_nights()
kkn_rate = kkn_nightly_rate()
kkn_total_rental_fee = int(kkn_total_nights) * int(kkn_rate)

More, it's usually a good idea to have one or two new lines after your imports.

Constants are usually upper-cased and defined at the top of the module, just after the imports, so:

any_more_ps_guests = "yes"
total_ps_rental_fee = 0

Should be:

ANY_MORE_PS_GUESTS = "yes"
TOTAL_PS_RENTAL_FEE = 0

More, you have another two similar constants which are basically exactly like the above ones (regarding the behaviour). Instead of defining them again just give the above more descriptive/general names.


Defining a function vs Calling a function

You don't have to define your functions each time. You can do that outside the while loop and just call it when you need to. More, you defined each function twice in each of your loops which is a waste of memory. When you call a function you are basically just telling the program to execute the function you already defined and is there.


DRY

In software engineering, don't repeat yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds.

In both loops, you have the same piece of code repeated which means you can put that inside a function and call it whenever you need.


As is, you code is hard to follow. Try to write small functions which do only one thing. For example, this:

ps_arrival_date_input = input("Arrival Date (xx/xx/xxxx):  ")
ps_arrival_date_month = int(ps_arrival_date_input[:2])
ps_arrival_date_day = int(ps_arrival_date_input[3:5])
ps_arrival_date_year = int(ps_arrival_date_input[6:])
ps_arrival_date = ps_arrival_date_month, ps_arrival_date_day, ps_arrival_date_year

Can become:

def split_date(input_date):
    return input_date.split('/')

Which you can later use like this:

arrival_date = input("Arrival Date (xx/xx/xxxx): ")
month, day, year = split_date(arrival_date)

This:

def ps_nightly_rate():
    rate = input('Rate: ')
    return rate

Might be rewritten like this:

def ps_nightly_rate():
    return input('Rate: ')

Your number_of_nights function might also be rewritten as:

def number_of_nights(departure_day, arrival_day):
    date_format = '%m/%d/%Y'
    return (datetime.strptime(arrival_day, date_format) - datetime.strptime(departure_day, date_format)).days

So, the first part of your code might look like this:

from datetime import datetime

GUESTS = "yes"
TOTAL_FEE = 0


def get_user_input(name, arrival, departure, rate):
    return input(name), input(arrival), input(departure), input(rate)


def number_of_nights(departure_day, arrival_day):
    date_format = '%m/%d/%Y'
    return (datetime.strptime(departure_day, date_format) - datetime.strptime(arrival_day, date_format)).days


while GUESTS:
    guest_name, arrival_date, departure_date, rate = get_user_input(
        'Pacific Shores Guest Name:',
        'Arrival Date (MM/DD/YYYY):',
        'Departure Date (MM/DD/YYYY):',
        'Rate:'
    )

    ps_total_nights = number_of_nights(departure_date, arrival_date)
    ps_total_rental_fee = int(ps_total_nights) * int(rate)

    TOTAL_FEE += ps_total_rental_fee

    anyone_else = input('Are there anymore guests?  ')
    if anyone_else == 'no':
        break

As you can see, you didn't even have to bother to get separately the days, months, year. I'm sure from now on you'll know how to finish it (Unfortunately I gotta go).

\$\endgroup\$
  • \$\begingroup\$ Wow, thank you very much for your insight! I'm just beginning so I know I'll make mistakes and write more code than I should and that's why I came here to ask for any advice. It's great to learn from those with much more experience! Thanks again for your response and great insight! \$\endgroup\$ – Jeremy Baker Aug 6 '17 at 16:04
3
\$\begingroup\$

Use better functions

The first and most important thing to note with your code is how it is very much repeated for each property. You should abstract things more so the computation is written once but used several time. This will allow you to fix bugs at a single place in the future if you ever find one.

For now, the two while loops and a bit of logic around are exactly the same; only the name of the property change. You also have duplicated logic to retrieve a date; only the word 'Arrival' or 'Departure' being different.

A first and very naïve rewrite could lead to:

import datetime


def ask_date(kind):
    date_input = input('{} Date (xx/xx/xxxx):  '.format(kind))
    date_month = int(date_input[:2])
    date_day = int(date_input[3:5])
    date_year = int(date_input[6:])
    return datetime.date(date_year, date_month, date_day)


def property_rental_fee(property_name):
    any_more_guests = "yes"
    total_rental_fee = 0

    while any_more_ps_guests == "yes":

        guest_name = input('{} Guest Name:  '.format(property_name))
        arrival_date = ask_date('Arrival')
        departure_date = ask_date('Departure')
        total_nights = (departure_date - arrival_date).days
        rate = input('Rate:  ')
        rental_fee = int(total_nights) * int(rate)

        print([guest_name, total_nights, rate, rental_fee])

        total_rental_fee += rental_fee

        anyone_else = input('Are there anymore guests?  ')
        if anyone_else == 'no':
            break

    print(property_name, 'Total Rental Fee:  ', total_rental_fee)
    return total_rental_fee


total_ps_rental_fee = property_rental_fee('Pacific Shores')
total_kkn_rental_fee = property_rental_fee('Kihei Kai Nani')

print('Total Rental Fee Amount: ', total_ps_rental_fee + total_kkn_rental_fee)

total_management_fee = (total_ps_rental_fee + total_kkn_rental_fee) * 0.10

print('Total Management Fee:  ', '{0:.2f}'.format(total_management_fee))

Simplify a bit

Extracting a date out of a string is exactly the purpose of datetime.strptime, no need to reivent the wheel. You can also get rid of the any_more_guests variable as it is never modified; and a few other cleaning bits:

import datetime


def ask_date(kind):
    date_input = input('{} Date (xx/xx/xxxx):  '.format(kind))
    return datetime.datetime.strptime(date_input, '%m/%d/%Y')


def property_rental_fee(property_name):
    prompt = '{} Guest Name:  '.format(property_name)
    total_rental_fee = 0

    while True:
        guest_name = input(prompt)
        arrival_date = ask_date('Arrival')
        departure_date = ask_date('Departure')
        total_nights = (departure_date - arrival_date).days
        rate = int(input('Rate:  '))
        rental_fee = total_nights * rate

        print([guest_name, total_nights, rate, rental_fee])

        total_rental_fee += rental_fee

        anyone_else = input('Are there anymore guests?  ')
        if anyone_else not in ('y', 'yes'):
            break

    print(property_name, 'Total Rental Fee:  ', total_rental_fee)
    return total_rental_fee


total_ps_rental_fee = property_rental_fee('Pacific Shores')
total_kkn_rental_fee = property_rental_fee('Kihei Kai Nani')

grand_total = total_ps_rental_fee + total_kkn_rental_fee
print('Total Rental Fee Amount: ', grand_total)

total_management_fee = grand_total * 0.10
print('Total Management Fee:  {0:.2f}'.format(total_management_fee))

Standardize your program

It is common practice to not keep code at the top-level of the file and rather use a "main" function. The idea being that it is then possible to import the file in an interactive session or a unit-test or whatever and test each function individually rather than being prompted by various inputs. You can use an if __name__ == '__main__': clause for that:

import datetime


def ask_date(kind):
    date_input = input('{} Date (xx/xx/xxxx):  '.format(kind))
    return datetime.datetime.strptime(date_input, '%m/%d/%Y')


def property_rental_fee(property_name):
    prompt = '{} Guest Name:  '.format(property_name)
    total_rental_fee = 0

    while True:
        guest_name = input(prompt)
        arrival_date = ask_date('Arrival')
        departure_date = ask_date('Departure')
        total_nights = (departure_date - arrival_date).days
        rate = int(input('Rate:  '))
        rental_fee = total_nights * rate

        print([guest_name, total_nights, rate, rental_fee])

        total_rental_fee += rental_fee

        anyone_else = input('Are there anymore guests?  ')
        if anyone_else not in ('y', 'yes'):
            break

    print(property_name, 'Total Rental Fee:  ', total_rental_fee)
    return total_rental_fee


def main():
    total_ps_rental_fee = property_rental_fee('Pacific Shores')
    total_kkn_rental_fee = property_rental_fee('Kihei Kai Nani')

    grand_total = total_ps_rental_fee + total_kkn_rental_fee
    print('Total Rental Fee Amount: ', grand_total)

    total_management_fee = grand_total * 0.10
    print('Total Management Fee:  {0:.2f}'.format(total_management_fee))


if __name__ == '__main__':
    main()

Sanitize your inputs

Your should not blindly rely on any value entered by the user to be valid. If 06/31/2018 was entered for a date or 199.99 for the rate, your program would crash. You should either:

  • handle it gracefully; or
  • ask until a valid value is found.

I would also simplify a bit asking for a new guest:

import datetime


def ask_date(kind):
    while True:
        date_input = input('{} Date (xx/xx/xxxx):  '.format(kind))
        try:
            return datetime.datetime.strptime(date_input, '%m/%d/%Y')
        except ValueError as error:
            print('Wrong Date:', error)


def ask_rate():
    while True:
        rate = input('Rate:  ')
        try:
            return int(rate)
        except ValueError as error:
            print('Wrong rate:', error)    


def property_rental_fee(property_name):
    prompt = '{} Guest Name (leave empty if no more guests):  '.format(property_name)
    total_rental_fee = 0

    while True:
        guest_name = input(prompt)
        if not guest_name:
            break

        arrival_date = ask_date('Arrival')
        departure_date = ask_date('Departure')
        rate = ask_rate()

        total_nights = (departure_date - arrival_date).days
        rental_fee = total_nights * rate
        total_rental_fee += rental_fee

        print([guest_name, total_nights, rate, rental_fee])

    print(property_name, 'Total Rental Fee:  ', total_rental_fee)
    return total_rental_fee


def main():
    total_ps_rental_fee = property_rental_fee('Pacific Shores')
    total_kkn_rental_fee = property_rental_fee('Kihei Kai Nani')

    grand_total = total_ps_rental_fee + total_kkn_rental_fee
    print('Total Rental Fee Amount: ', grand_total)

    total_management_fee = grand_total * 0.10
    print('Total Management Fee:  {0:.2f}'.format(total_management_fee))


if __name__ == '__main__':
    main()

More genericity

The last bit I would change is the ability to add new properties by a single change in the code. I would use variable number of arguments for the main function and work on iterables rather than using a variable per property:

import datetime


def ask_date(kind):
    while True:
        date_input = input('{} Date (xx/xx/xxxx):  '.format(kind))
        try:
            return datetime.datetime.strptime(date_input, '%m/%d/%Y')
        except ValueError as error:
            print('Wrong Date:', error)


def ask_rate():
    while True:
        rate = input('Rate:  ')
        try:
            return int(rate)
        except ValueError as error:
            print('Wrong rate:', error)    


def property_rental_fee(property_name):
    prompt = '{} Guest Name (leave empty if no more guests):  '.format(property_name)
    total_rental_fee = 0

    while True:
        guest_name = input(prompt)
        if not guest_name:
            break

        arrival_date = ask_date('Arrival')
        departure_date = ask_date('Departure')
        rate = ask_rate()

        total_nights = (departure_date - arrival_date).days
        rental_fee = total_nights * rate
        total_rental_fee += rental_fee

        print([guest_name, total_nights, rate, rental_fee])

    print(property_name, 'Total Rental Fee:  ', total_rental_fee)
    return total_rental_fee


def main(*properties_names):
    grand_total = sum(map(property_rental_fee, properties_names))
    print('Total Rental Fee Amount: ', grand_total)

    total_management_fee = grand_total * 0.10
    print('Total Management Fee:  {0:.2f}'.format(total_management_fee))


if __name__ == '__main__':
    main('Pacific Shores', 'Kihei Kai Nani')

And to make it even more better you could add docstrings to the mix.

\$\endgroup\$
  • \$\begingroup\$ Thank you so much for your helpful advice as it is definitely going to help me become more productive and efficient with Python! \$\endgroup\$ – Jeremy Baker Aug 6 '17 at 16:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.