6
\$\begingroup\$

Main purpose of this code,

  1. Take some information for animal features
  2. Clasification of user input
  3. print those input into txt file

My problem is that, I think coding of animal features takes too much lines. Is there any way to do that in a simple way. Something like putting all the features(nutrition etc) into animal, also code a input function then take the all input from user by this function easly.

Sorry if i m totally wrong. I m just trying to figure out coding.

# all class


class animal:

    def __init__(self, nutrition = "gg", respiratory = "gg", excretory = "gg", reproductive = "gg"):
        self.nutrition = nutrition
        self.respiratory = respiratory
        self.excretory = excretory
        self.reproductive = reproductive


class land(animal):

    def __init__(self, nutrition, respiratory, excretory, reproductive, climate, animal_type):
        super().__init__(nutrition, respiratory, excretory, reproductive)
        self.climate = climate
        self.animal_type = animal_type

    def land_show_info(self):
        return "Animal Type: {}\nNutrition: {}\nRespiratory: {}\nExcretory: {}\nReproductive: {}\nClimate: {}\n".format(
            self.animal_type, self.nutrition, self.respiratory, self.excretory, self.reproductive, self.climate)


class sea(animal):

    def __init__(self, nutrition, respiratory, excretory, reproductive, climate, animal_type):
        super().__init__(nutrition, respiratory, excretory, reproductive)
        self.climate = climate
        self.animal_type = animal_type

    def land_show_info(self):
        return "Animal Type: {}\nNutrition: {}\nRespiratory: {}\nExcretory: {}\nReproductive: {}\nClimate: {}\n".format(
            self.animal_type, self.nutrition, self.respiratory, self.excretory, self.reproductive, self.climate)


class air(animal):

    def __init__(self, nutrition, respiratory, excretory, reproductive, climate, animal_type):
        super().__init__(nutrition, respiratory, excretory, reproductive)
        self.climate = climate
        self.animal_type = animal_type

    def land_show_info(self):
        return "Animal Type: {}\nNutrition: {}\nRespiratory: {}\nExcretory: {}\nReproductive: {}\nClimate: {}\n".format(
            self.animal_type, self.nutrition, self.respiratory, self.excretory, self.reproductive, self.climate)


# all class input function
def nutrition():
    while True:
        nutrition = input("""
        Please Enter Nutrition Type
        1. Carnivorous    --> 'c'
        2. Herbivorous    --> 'h'
        3. Omnivorous     --> 'o'
        4. No Information --> 'n'\n""")
        if nutrition == 'c':
            nutrition = "Carnivorous"
            break
        elif nutrition == 'h':
            nutrition = "Herbivorous"
            break
        elif nutrition == 'o':
            nutrition = "Omnivorous"
            break
        elif nutrition == 'n':
            nutrition = "No Information"
            break
        else:
            print("""!WARNING!
            ...Improper Input Detected...""")
    return nutrition


def respiratory():
    while True:
        respiratory = input("""
        Please Enter Respiratory Type
        1. with Oxygen    --> '+o2'
        2. without Oxygen --> '-o2'
        3. No Information --> 'n'\n""")
        if respiratory == '+o2':
            respiratory = "with Oxygen"
            break
        elif respiratory == '-o2':
            respiratory = "without Oxygen"
            break
        elif respiratory == 'n':
            respiratory = "No Information"
            break
        else:
            print("""!WARNING!
            ...Improper Input Detected...""")
    return respiratory


def excretory():
    while True:
        excretory = input("""
        Please Enter Excretory Type
        1. Ammonia         --> 'a'
        2. Urea           --> 'u'
        3. Uric Acid      --> 'ua'
        4. No Information --> 'n'\n""")
        if excretory == 'a':
            excretory = "Ammonia"
            break
        elif excretory == 'u':
            excretory = "Urea"
            break
        elif excretory == 'ua':
            excretory = "Uric Acid"
            break
        elif excretory == 'n':
            excretory = "No Information"
            break
        else:
            print("""!WARNING!
            ...Improper Input Detected...""")
    return excretory


def reproductive():
    while True:
        reproductive = input("""
        Please Enter Reproductive Type
        1. Sexual         --> 's'
        2. Asexual        --> 'a'
        3. No Information --> 'n'\n""")
        if reproductive == 's':
            reproductive = "Sexual"
            break
        elif reproductive == 'a':
            reproductive = "Asexual"
            break
        elif reproductive == 'n':
            reproductive = "No Information"
            break
        else:
            print("""!WARNING!
            ...Improper Input Detected...""")
    return excretory


def climate():
    while True:
        climate = input("""
        Please Enter Climate Type
        1. Desert         --> 'd'
        2. Forest         --> 'f'
        3. Tundra         --> 't'
        4. Ice Field      --> 'i'
        5. No Information --> 'n'\n""")
        if climate == 'd':
            climate = "Desert"
            break
        elif climate == 'f':
            climate = "Forest"
            break
        elif climate == 't':
            climate = "Tundra"
            break
        elif climate == 'i':
            climate = "No Ice Field"
            break
        elif climate == 'n':
            climate = "No Information"
            break
        else:
            print("""!WARNING!
            ...Improper Input Detected...""")
    return climate


def animal_type():
    while True:
        animal_type = input("""
        Please Enter Animal Type
        1. Land --> 'l'
        2. Sea  --> 's'
        3. Air  --> 'a'\n""")
        if animal_type == 'l':
            animal_type = "Land"
            break
        elif animal_type == 's':
            animal_type = "Sea"
            break
        elif animal_type == 'a':
            animal_type = "Air"
            break
        else:
            print("""!WARNING!
            ...Improper Input Detected...""")
    return animal_type


# input from user
nutrition = nutrition()
respiratory = respiratory()
excretory = excretory()
reproductive = reproductive()
climate = climate()
animal_type = animal_type()

# animal classification
if animal_type == 'Land':
    animal1 = land(nutrition, respiratory, excretory, reproductive, climate, animal_type)
    print(animal1.land_show_info())
elif animal_type == 'Sea':
    animal1 = sea(nutrition, respiratory, excretory, reproductive, climate, animal_type)
    print(animal1.land_show_info())
else:
    animal1 = air(nutrition, respiratory, excretory, reproductive, climate, animal_type)
    print(animal1.land_show_info())

# Is there a better way to check file is there or not by program itself
while True:
    file_ = input("""Is there a file on C:/Users/Gökberk/Desktop/Animal List.txt directory\n(y/n)""")
    if file_ == "y":
        with open("C:/Users/Gökberk/Desktop/Animal List.txt", "a", encoding="utf-8") as file:
            file.write("##############################\n")
            file.write(animal1.land_show_info())
            break
    elif file_ == "n":
        with open("C:/Users/Gökberk/Desktop/Animal List.txt", "w", encoding="utf-8" ) as file:
            file.write("...Welcome to Animal List File...\n")
            file.write("##############################\n")
            file.write(animal1.land_show_info())
        print("File has been created to C:/Users/Gökberk/Desktop/Animal List.txt")
        break
    else:
        print("""!WARNING!
            ...Improper Input Detected...""")

print("Program is Over")
\$\endgroup\$

2 Answers 2

10
\$\begingroup\$

Nomenclature

The standard capitalization for classes is TitleCase, i.e.

class Animal:
class Land:
class Sea:
class Air:

Parent methods

Your land_show_info() should be moved to Animal. It does not need to be re-implemented in each of the children.

Interpolation

This:

"Animal Type: {}\nNutrition: {}\nRespiratory: {}\nExcretory: {}\nReproductive: {}\nClimate: {}\n".format(
        self.animal_type, self.nutrition, self.respiratory, self.excretory, self.reproductive, self.climate)

is more easily expressed as

(
    f'Animal Type: {self.animal_type}\n'
    f'Nutrition: {self.nutrition}\n'
    f'Respiratory: {self.respiratory}\n'
    f'Excretory: {self.excretory}\n'
    f'Reproductive: {self.reproductive}\n'
    f'Climate: {self.climate}\n'
)

Enumerations

You should make an enum.Enum class to represent the nutrition type:

class Nutrition(enum.Enum):
    CARNIVOROUS = 'c'
    HERBIVOROUS = 'h'
    OMNIVOROUS = 'o'
    NO_INFORMATION = 'n'

Then your input routine can be (for example)

def nutrition():
    prompt = (
        'Please Enter Nutrition Type\n' +
        '\n'.join(
            f"{i}. {nut.name.title():15} --> '{nut.value}'"
            for i, nut in enumerate(Nutrition)
        ) + '\n'
    )
    while True:
        nut_value = input(prompt)
        try:
            return Nutrition(nut_value)
        except ValueError:
            print(f"""!WARNING!
            ...Improper Input {nut_value} Detected...""")

The return value of your nutrition() function will then have a more useful type than str. The same applies to your respiratory, animal_type, climate, reproductive and excretory input methods.

Shadowing

Since you have a method called nutrition, do not also name a variable nutrition.

Global code

Starting with these lines onward:

# input from user
nutrition = nutrition()

you should pull out all of your global code into one or more methods.

Factory

You can change this:

# animal classification
if animal_type == 'Land':
    animal1 = land(nutrition, respiratory, excretory, reproductive, climate, animal_type)
    print(animal1.land_show_info())
elif animal_type == 'Sea':
    animal1 = sea(nutrition, respiratory, excretory, reproductive, climate, animal_type)
    print(animal1.land_show_info())
else:
    animal1 = air(nutrition, respiratory, excretory, reproductive, climate, animal_type)
    print(animal1.land_show_info())

to temporarily store a type and use it for construction:

animal_class = {
    'Land': land,
    'Sea': sea,
    'Air': air,
}[animal_type]

animal1 = animal_class(nutrition, respiratory, excretory, reproductive, climate, animal_type)
print(animal1.land_show_info())

Parametric paths

The paths in here:

file_ = input("""Is there a file on C:/Users/Gökberk/Desktop/Animal List.txt directory\n(y/n)""")
if file_ == "y":
    with open("C:/Users/Gökberk/Desktop/Animal List.txt", "a", encoding="utf-8") as file:
        file.write("##############################\n")
        file.write(animal1.land_show_info())
        break
elif file_ == "n":
    with open("C:/Users/Gökberk/Desktop/Animal List.txt", "w", encoding="utf-8" ) as file:
        file.write("...Welcome to Animal List File...\n")
        file.write("##############################\n")
        file.write(animal1.land_show_info())
    print("File has been created to C:/Users/Gökberk/Desktop/Animal List.txt")

should not be hard-coded. Accept them as command-line arguments, environmental variables or in a configuration file.

Example code

from dataclasses import dataclass
from enum import Enum, unique
from pathlib import Path
from typing import Type


class AnimalParam:
    # Python does not support extending Enum, so this is left as a mix-in

    @property
    def title(self: Enum) -> str:
        return self.name.title().replace('_', ' ')

    @classmethod
    def from_stdin(cls: Type[Enum]) -> 'Enum':
        prompt = (
            f'Please enter {cls.__name__} type\n' +
            '\n'.join(
                f"  '{v.value}' -> {v.title}"
                for v in cls
            ) + '\n'
        )
        while True:
            v = input(prompt)
            try:
                return cls(v)
            except ValueError:
                print(f'Invalid {cls.__name__} type "{v}"')


@unique
class Nutrition(AnimalParam, Enum):
    CARNIVOROUS = 'c'
    HERBIVOROUS = 'h'
    OMNIVOROUS = 'o'
    NO_INFORMATION = 'n'


@unique
class Respiratory(AnimalParam, Enum):
    WITH_OXYGEN = '+o2'
    WITHOUT_OXYGEN = '-o2'
    NO_INFORMATION = 'n'


@unique
class Excretory(AnimalParam, Enum):
    AMMONIA = 'a'
    UREA = 'u'
    URIC_ACID = 'ua'
    NO_INFORMATION = 'n'


@unique
class Reproductive(AnimalParam, Enum):
    SEXUAL = 's'
    ASEXUAL = 'a'
    NO_INFORMATION = 'n'


@unique
class Climate(AnimalParam, Enum):
    DESERT = 'd'
    FOREST = 'f'
    TUNDRA = 't'
    ICE_FIELD = 'i'
    NO_INFORMATION = 'n'


@unique
class Habitat(AnimalParam, Enum):
    LAND = 'l'
    SEA = 's'
    AIR = 'a'


@dataclass(frozen=True)
class Animal:
    habitat: Habitat
    nutrition: Nutrition
    respiratory: Respiratory
    excretory: Excretory
    reproductive: Reproductive
    climate: Climate

    def __str__(self) -> str:
        return '\n'.join(
            f'{k.title()}: {v.title}'
            for k, v in self.__dict__.items()
        )

    @classmethod
    def from_stdin(cls) -> 'Animal':
        return cls(**{
            field.name: field.type.from_stdin()
            for field in cls.__dataclass_fields__.values()
        })


def main():
    # input from user
    animal = Animal.from_stdin()
    print(animal)

    path = Path(input('Please enter the path to the list file: '))
    with path.open('a') as f:
        banner = 'Welcome to Animal List File.'
        f.write(
            f'{banner}\n'
            f'{"#" * len(banner)}\n\n'
            f'{animal}\n\n'
        )


if __name__ == '__main__':
    main()

Notable changes:

  • Use a mix-in class for formatting and input utilities
  • Rename "animal type" to "habitat" because the former was not clear enough to me
  • Use a dataclass with some shortcuts that assume that every member is an instance of our special enum
  • Use pathlib, and do not care whether the file already exists - append mode will take care of it
  • Global code moved to a main
\$\endgroup\$
6
  • 1
    \$\begingroup\$ Your answer really impressive. Thank you very much. I m really newbie in coding. I understand the way you do. However i couldn't do features part of the code(nutrition etc. can you do one of these features as an example. Then i might improve myself more. \$\endgroup\$
    – Gokberk
    Commented Apr 19, 2020 at 17:43
  • \$\begingroup\$ Edited; the suggested code iterates through all possible enum values. \$\endgroup\$
    – Reinderien
    Commented Apr 19, 2020 at 18:27
  • \$\begingroup\$ this is neat -- you could also make a single generic input function that takes the enum class as a parameter, so you could simply do nutrition = get_value(Nutrition) etc! \$\endgroup\$
    – Samwise
    Commented Apr 19, 2020 at 18:47
  • 1
    \$\begingroup\$ Quite right :) For the title in the prompt you could even use the enum's __name__. \$\endgroup\$
    – Reinderien
    Commented Apr 19, 2020 at 18:48
  • \$\begingroup\$ Nice answer. Instead of the \n in the string interpolation, you could use a multiline string \$\endgroup\$ Commented Apr 20, 2020 at 11:59
5
\$\begingroup\$

I agree with everything Reinderien said! I took a slightly different approach with the inputs to preserve your original strings/typing -- for example, there isn't any reason at all for the different animal subclasses once you move all the common functionality into the parent class, since 100% of the functionality is shared between all classes, but I'm preserving them under the assumption that you might have other code that wants them to be different Python types. I definitely like the idea of representing the different attributes as enums; it'd just be a little bit more work to format the text the exact way your original code does.

import os
from typing import List, Tuple


class Animal:
    def __init__(
        self,
        nutrition: str,
        respiratory: str,
        excretory: str,
        reproductive: str,
        climate: str,
        animal_type: str
    ):
        self.nutrition = nutrition
        self.respiratory = respiratory
        self.excretory = excretory
        self.reproductive = reproductive
        self.climate = climate
        self.animal_type = animal_type

    def __str__(self) -> str:
        return (
            f"Animal Type: {self.animal_type}\n"
            f"Nutrition: {self.nutrition}\n"
            f"Respiratory: {self.respiratory}\n"
            f"Excretory: {self.excretory}\n"
            f"Reproductive: {self.reproductive}\n"
            f"Climate: {self.climate}\n"
        )


class LandAnimal(Animal):
    pass


class SeaAnimal(Animal):
    pass


class AirAnimal(Animal):
    pass


def get_input(type: str, options: List[Tuple[str, str]]) -> str:
    while True:
        try:
            print(f"Please Enter {type} Type")
            for i, (opt, result) in enumerate(options):
                print(f"{i+1}. {result}".ljust(20) + f"--> '{opt}'")
            user_opt = input()
            return [opt for opt in options if opt[0] == user_opt][0][1]
        except Exception:
            print("""!WARNING!
            ...Improper Input Detected...""")


def get_nutrition() -> str:
    return get_input("Nutrition", [
        ("c", "Carnivorous"),
        ("h", "Herbivorous"),
        ("o", "Omnivorous"),
        ("n", "No Information")
    ])


def get_respiratory() -> str:
    return get_input("Respiratory", [
        ("+o2", "with Oxygen"),
        ("-o2", "without Oxygen"),
        ("n", "No Information")
    ])


def get_excretory() -> str:
    return get_input("Excretory", [
        ("a", "Ammonia"),
        ("u", "Urea"),
        ("ua", "Uric Acid"),
        ("n", "No Information")
    ])


def get_reproductive() -> str:
    return get_input("Reproductive", [
        ("s", "Sexual"),
        ("a", "Asexual"),
        ("n", "No Information")
    ])


def get_climate() -> str:
    return get_input("Climate", [
        ("d", "Desert"),
        ("f", "Forest"),
        ("t", "Tundra"),
        ("i", "Ice Field"),
        ("n", "No Information")
    ])


def get_animal_type():
    return get_input("Animal", [
        ("l", "Land"),
        ("s", "Sea"),
        ("a", "Air")
    ])


animal_type_class = {
    "Land": LandAnimal,
    "Sea": SeaAnimal,
    "Air": AirAnimal,
}

# input from user
nutrition = get_nutrition()
respiratory = get_respiratory()
excretory = get_excretory()
reproductive = get_reproductive()
climate = get_climate()
animal_type = get_animal_type()

animal = animal_type_class[animal_type](
    nutrition,
    respiratory,
    excretory,
    reproductive,
    climate,
    animal_type
)
print(animal)

exit()

path = "C:/Users/Gökberk/Desktop/Animal List.txt"
mode = "a" if os.path.isfile else "w"
with open(path, mode, encoding="utf-8") as file:
    file.write("##############################\n")
    file.write(str(animal))
if mode == "w":
    print(f"File has been created to {path}")

print("Program is Over")
\$\endgroup\$
3
  • 1
    \$\begingroup\$ it'd just be a little bit more work to format the text the exact way your original code does - With actual enums, it's probably actually less work than your get_input which currently requires that you pass everything in that would already exist in an enum. It's not terrible, but enums will still make this easier for you. You can see my answer for an example. \$\endgroup\$
    – Reinderien
    Commented Apr 19, 2020 at 18:30
  • \$\begingroup\$ oh good point, I forgot that title() would convert the underscores! \$\endgroup\$
    – Samwise
    Commented Apr 19, 2020 at 18:45
  • \$\begingroup\$ It won't, actually. You would need to replace those. \$\endgroup\$
    – Reinderien
    Commented Apr 19, 2020 at 18:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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