3
\$\begingroup\$
#Creates the Animal class.
class Animal:
    def __init__(self, name, age, type_of_animal):
        self.name = name
        self.age = age
        self.animal_type = type_of_animal
    
    def bio(self):
        
        #Checks if the animal's type begins with a vowel.
        if self.animal_type[0].lower() in "aeiou":
            
            #Checks if the animal is older than 1.
            if self.age >= 2:
                print(f"{self.name.capitalize()} is an {self.animal_type.capitalize()} and they are {self.age} years old.")
            else:
                 print(f"{self.name.capitalize()} is an {self.animal_type.capitalize()} and they are {self.age} year old.")
        
        #If it isn't a vowel it's a consonant.
        else:
            if self.age >= 2:
                 print(f"{self.name.capitalize()} is a {self.animal_type.capitalize()} and they are {self.age} years old.")
            else:
                 print(f"{self.name.capitalize()} is a {self.animal_type.capitalize()} and they are {self.age} year old.")

#Animals                
a1 = Animal("milo", 1, "tree frog")
a2 = Animal("diego", 2, "anaconda")
a3 = Animal("paul", 1, "aardvark")
a4 = Animal("pietro", 7, "cheetah")
a5 = Animal("bob", 1, "dog")

#Animal list
animal_list = [a1,a2,a3,a4,a5]

#Adds more animals to the list
animal_list.append(Animal("kally", 5, "horse"))
animal_list.append(Animal("oni", 1, "python"))
animal_list.append(Animal("kat", 4, "ocelot"))

#Applies .bio() method to each individual animal in the list.
for i in range(len(animal_list)):
    animal_list[i].bio()

This code defines a system for creating animal objects (such as a frog, anaconda, and ocelot) and provides a way to generate a simple bio for each animal based on its type and age. It's a basic example of object-oriented programming (OOP) in Python, where each animal is an object with attributes like name, age, and species. The goal of the code is to create animals, store them in a list, and then display personalized bios for each animal in a user-friendly format. How can I make this more efficient?

\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

DRY

There is a lot of repeated code in the bio function. You repeat the age check twice and the print 4 times with few modifications. If you create 2 new variables for the 2 types of checks, you only need 1 print:

def bio(self):
    # Checks if the animal's type begins with a vowel.
    article = 'an' if self.animal_type[0].lower() in "aeiou" else 'a'

    # Checks if the animal is older than 1.
    year = 'years' if self.age >= 2 else 'year'

    print(f"{self.name.capitalize()} is {article} {self.animal_type.capitalize()} and they are {self.age} {year} old.")

Naming

You did a good job of naming the class and variables, and you adhered to the recommended naming style in the PEP 8 style guide.

However, the function named bio is not very descriptive. I recommend something like print_biography.

Documentation

PEP 8 recommends adding a docstring to describe the classes and functions. You can convert some of the comments to docstrings. For example:

#Creates the Animal class.
class Animal:

would be something like:

class Animal:
    """ A generic animal """

The function docstring could be:

def bio(self):
    """ Print the biographical data for the animal """

Input checking

I recommend adding checks for the data types. For example, if someone enters -3 for the age, you should report an error. Also check for large values and invalid input like "dog" for the age (in case someone typed data in the wrong order).

Layout

Since you are new to Python, there are some simple development tools that you might not be aware of.

The code uses some inconsistent indentation and other uses of whitespace. The black program can be used to automatically format your code consistently.

The pylint linting tool can also be used to identify formatting and other issues.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I would say to prefer ruff check over pylint. The pylint tool offered yeoman's service for years, and has done much good in the world. But nowadays? Go with the far quicker ruff. Also, it offers kinder gentler default settings. For example pylint often flags C0114 / C0115 / C0116 for a missing module / class / function docstring. I feel it's better to omit an obvious redundant docstring for a trivial def get_plural(n: float)->str: function, and that's always a matter for human judgement rather than an automated flag. But there are diverse opinions on this. \$\endgroup\$
    – J_H
    Commented 9 hours ago
2
\$\begingroup\$

Thank you for sharing. There's some good things going on in this code.

constructor

    def __init__(self, name, age, type_of_animal):
        self.name = name
        self.age = age
        self.animal_type = type_of_animal

This is bog standard and perfectly clear, so you can keep it as it is.

The self.foo = foo pattern comes up pretty often, so once you're comfortable with this pattern you might want to use the @dataclass decorator:

from dataclasses import dataclass

@dataclass
class Animal:
    name
    age
    type_of_animal

Or better, you might choose to add optional type annotation:

@dataclass
class Animal:
    name: str
    age: float
    type_of_animal: str

DRY

We have four print() statements that are pretty repetitive. It's great that you wrote this out and produced correct results.

But once you have discovered the true needs of the code, there's an opportunity to instrospect, study, go back and DRY up the code so you don't repeat yourself so much.

The pair of detailed output requirements you discovered are

  • "a" / "an" depending on initial vowel
  • "s" plural, depending on age

The simplest approach would be to define temp variables which then appear in a single unified print() statement.

    article = "an" if self.animal_type[0].lower() in "aeiou" else "a"
    plural = "s" if self.age >= 2 else ""

Another option would be to define a pair of trivial helper functions which accomplish the same thing. Each helper function would return a string, either "a" / "an", or "" / "s".

design of Public API

The .bio() method is evaluated for side effects -- displaying text on stdout. In many situations it would be more convenient to return a str from that method, and let the caller print() the string as needed. This would make it easy to define automated unit tests for your code.

iterating

This certainly works, but it is slightly clunky:

for i in range(len(animal_list)):
    animal_list[i].bio()

Prefer this more pythonic approach.

for animal in animal_list:
    animal.bio()
\$\endgroup\$
2
\$\begingroup\$

In addition to what the others have said, it would make your class more flexible if Animal.bio returns a string rather than printing one itself.

You can still:

for animal in animal_list:
    print(animal.bio())

But if you want to output that data to a file, for instance, you wouldn't need to write a new "bio" function.

\$\endgroup\$

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.