Days 2-8 of my #100DaysOfCode journey: Whew!

jenad88

John Enad

Posted on April 8, 2023

Days 2-8 of my #100DaysOfCode journey: Whew!

During days 2-8 of my #100DaysOfCode journey, I faced some challenges. Although I breezed through simpler concepts in the beginning, work picked up as we entered the second quarter of the year. Despite this, I persevered and completed lessons from Day 4 through Day 24 of the Udemy course. Topics covered included randomization, lists, for-loops, range, functions, dictionaries, basic Turtle graphics, and even a Snake game project, which was incredibly fun. I concluded with File, Directories, and Paths at Day 24.

At this point, I decided to delve deeper into Object-Oriented Programming in Python. I found "Python 3: Object-Oriented Programming" by Dusty Phillips on my Packt Publishing account and worked through nearly half of the book before taking a breather.
The author's explanations were excellent. I would highly recommend it.

I was pleasantly surprised by the similarities between Java and Python, despite some syntax differences. The code snippet below demonstrates familiar concepts such as classes, inheritance, constructors, super, getters, setters, try-catch-finally, custom exceptions, throwing (raising) exceptions, overriding, and toString (repr). I found even more similarities as I continued. Here are a few examples:

class InvalidAge(Exception):
    pass


class Thing:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError("Name should be string")
        self._name = value

    @name.deleter
    def name(self):
        """ this would be weird though in this particular case """
        print("Name deleted")
        del self._name

    def __repr__(self):
        return f"{self.name}"


class Person:
    def __init__(self, name):
        self.name = name

    def say_something(self):
        print(f"Hello, my name is {self.name}")

    def __repr__(self):
        return f"{self.name}"


class Friend(Person):
    def __init__(self, name, email, age):
        super().__init__(name)
        self.email = email
        if not isinstance(age, int):
            raise TypeError("Age should be integer")
        if age < 1 or age >= 120:
            raise InvalidAge("Age should be greater than 0 or less than 120")
        self.age = age

    def say_something(self):
        print(f"Hey, What's up?")

    def __repr__(self):
        return f"{self.name} {self.email} {self.age}"


if __name__ == "__main__":
    try:
        person = Person("John")
        friend = Friend("Jack", "jack@test", 70)
        thing = Thing("thing1")
        print(person)
        print(friend)
        print(thing.name)
        del thing.name
    except TypeError as e:
        print("Error: ", e)
    finally:
        print("finished")
Enter fullscreen mode Exit fullscreen mode

As you can see, a lot of familiar things are demonstrated above like classes, inheritance, constructors, super, getters, setters, try-catch-finally, custom exceptions, throwing (raising) exceptions, overriding and toString (repr). There are even more similarities that I found.

Upon reaching the Data Structures chapter, things became more exciting. I was already familiar with the concept of tuples,
which are immutable objects that store other objects, from my experience with Java and C#. I was pleasantly surprised by tuple unpacking, which is similar to JavaScript's destructuring assignment statement.

name, email, age = ("Matt", "matt@example.com", 25)
print(name, email, age)
Enter fullscreen mode Exit fullscreen mode

Named tuples and data classes were entirely new concepts for me. Named tuples are created using a factory function in the collections package, while data classes are similar to tuples but allow for mutable attributes.

from collections import namedtuple

Superhero = namedtuple("Superhero", ["name", "alias", "level"])
superman = Superhero(name="superman", alias="Clark Kent", level=95)
print(superman)
print(superman.name)
print(superman.alias)
print(superman.level)
Enter fullscreen mode Exit fullscreen mode

Dataclasses on the other hand are almost quite the same as Tuple in that it allows named fields however, one huge difference is that Dataclasses are not immutable. It's attributes can be updated and one could even add more attributes to them.

from dataclasses import make_dataclass

Superhero = make_dataclass("Superhero", ["name", "alias", "level"])
superman = Superhero(name="superman", alias="Clark Kent", level=95)
print(superman)
print(superman.name)
print(superman.alias)
print(superman.level)
Enter fullscreen mode Exit fullscreen mode

A more common way to define dataclasses is to use a class decorate called @dataclass

from dataclasses import dataclass

@dataclass
class Superhero:
    name: str
    alias: str
    level: int

superman = Superhero(name="superman", alias="Clark Kent", level=95)
print(superman)
print(superman.name)
print(superman.alias)
print(superman.level)
Enter fullscreen mode Exit fullscreen mode

Dictionaries seemed pretty standard at first glance. However, defaultdict and Counter methods really caught my attention due to their elegance and compactness.

superheroes = {
    "superman": ("Clark Kent", 95),
    "batman": ("Bruce Wayne", 40)
}

print(superheroes["superman"])
print(superheroes["batman"])
Enter fullscreen mode Exit fullscreen mode

As expected, we get a KeyError when we try to access a key that doesn’t exist in the dictionary.

print(superheroes["flash"])

And one way around it is the use the get method on the dictionary:

print(superheroes.get("flash", "Not a superhero"))

Now, these next two methods really caught me by surprise and ones that I really like: defaultdict and Counter.

from collections import defaultdict
def num_frequency(num_list):
    totals = defaultdict(int)
    for value in num_list:
        totals[value] += 1
    return totals

numbers = [5, 3, 2, 5, 2]
print(num_frequency(numbers))
Enter fullscreen mode Exit fullscreen mode

The code above figures out the frequency of numbers and puts them in a dictionary. But, if you think it's not possible to make
the code even more compact, you're in for a big surprise. I certainly was:

from collections import Counter

numbers = [5, 3, 2, 5, 2]
print(Counter(numbers))
Enter fullscreen mode Exit fullscreen mode

Guess what, it does the same thing but with even less code!

And another equally impressive example:

from collections import Counter

responses = ["superman", "batman", "superman", "superman", "flash", "batman", "superman"]

print(f"The children voted for {Counter(responses).most_common(1)[0][0]}")
print(f"The number of votes for this superhero were: {Counter(responses).most_common(1)[0][1]}")
Enter fullscreen mode Exit fullscreen mode

It's only been eight days, and I feel like I'm making excellent progress. I hope to maintain this pace for the remaining 92 days.
Until next time!

💖 💪 🙅 🚩
jenad88
John Enad

Posted on April 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related