Python Objects & Functions: A Technical Dive
Tags: python, beginners, coding, programming
Published: 2024-02-09 15:30:35

So far in my journey coding with Python one of the lessons I enjoyed learning about so far was how to remember objects. Let's look at some code and break down some of the topics. For disclosure, to make this entertaining for myself and you (the reader), I choose to write some of the code myself (Python experts have mercy). However, we will be looking at some fun places to go for a night out in Brooklyn, and if you look through some of what I've written, you might find a fun place to go to if you happen to visit NYC. The treasure for you here (if you're 21+) is that if you reach the end of the blog, I won't gatekeep..oh and you might learn a thing or two about Python also.

To begin:

```
class Bar:
    def __init__(self, name, music_type):
        self.name = name
        self.music_type = music_type
```

The code above defines a Python class named Bar. This class serves as a blueprint for creating objects (instances) that represent various bars, taking in each bar's characteristics: its name, and the type of music it plays. Let's break down the components of this class definition to understand its structure and purpose:

```
class Bar:
```

The easiest one to start with is the class. In Python, the class keyword is followed by the class name (Bar here), and a colon begins the definition of a Bar class, serving as a template for creating objects.

Next, we have this string of code:

```
def __init__(self, name, music_type):
```

Whose method and its set-up you will become very familiar with in Python. This line introduces a method you'll frequently encounter in Python: the constructor. Known as __init__, this special method is triggered automatically whenever a new instance of the class is created. It serves to initialize the new object with its distinct characteristics— the bar's name and the type of music it plays for example.

**self**: Represents the instance of the class and allows access to the attributes and methods of the class. It's how you refer to the instance within the class's scope.

**name and music_type**: These parameters are passed to the __init__ method and are used to set up the new object with its specific name and music type, making each instance unique.

Next, we have the:

```
        self.name = name
        self.music_type = music_type
```

**self**: This keyword refers to the instance of the Bar class that's currently being created. It's how we access attributes and methods within the class instance.

**.name**: By appending .name to self, we specify that we're dealing with the name attribute of this particular instance. It's like saying, "In this bar object, there's an attribute named name."

**= name**: The assignment operator (=) is used here to assign the value passed into the name parameter of the __init__ method to the instance's name attribute. The name on the right side of = is the argument supplied when a new Bar instance is being instantiated.

For the **self.music_type** assignment, you would follow the same guidelines.

However, let's make a slight modification here now that you know some of the breakdown of the original code, which might make this part a bit easier to understand:

```
class Bar:
    all = []  # Class variable to store instances

    def __init__(self, name, music_type):
        self.name = name
        self.music_type = music_type
        Bar.all.append(self)  # Adding each new instance to our class variable
```
With this modification, every new Bar instance created is automatically remembered by appending itself to the "all" list. This method allows us to easily manage and access the list of bars I've been hinting at, making it possible to iterate over them, display their details, or even filter them based on certain criteria like music type. Let's take a look at what this looks like:

(What you've been waiting for, but please read until the end) The list:

```
pencil_factory = Bar("Pencil Factory", "Indie Rock")
twinz_lounge = Bar("Twinz Lounge", "Reggae")
jupiter_disco = Bar("Jupiter Disco", "Electronic")
public_records = Bar("Public Records", "Various Live Music")
bar_schimanski = Bar("Bar Schimanski", "Techno")
carousel = Bar("Carousel", "Pop")
westlight = Bar("Westlight", "Jazz")

```

To reiterate, by adding each new instance of Bar to the "all []" class variable, we create a dynamic list that grows every time a new bar is introduced to our list above. 

To further refine this, I implemented class methods to interact with our collection of bars:

```
class Bar:
    all = []

    def __init__(self, name, music_type):
        self.name = name
        self.music_type = music_type
        Bar.add_bar_to_all(self)

    @classmethod
    def add_bar_to_all(cls, bar):
        cls.all.append(bar)

    @classmethod
    def show_bar_names(cls):
        for bar in cls.all:
            print(f"{bar.name} plays {bar.music_type} music.")

```
Let's break down the elements:

**@classmethod**: This is a decorator in Python that alters the way the method add_bar_to_all(cls, bar) is called and used. Unlike regular methods, which take the instance self as the first argument, a class method takes the class cls as the first argument. This means that the method belongs to the class rather than any individual instance of the class. It can be called on the class itself, rather than on an instance, affecting all instances of the class.

**def add_bar_to_all(cls, bar)**: This defines a class method named add_bar_to_all. The method is designed to take two arguments: cls, which is a reference to the class, and bar, which is an instance of the Bar class to be added to the class variable all.

**cls**: This is a conventional name for the first argument to class methods (similar to how self is used for instance methods). It refers to the class on which the method is called. This allows the method to access class variables and other class methods.

**bar**: This is the second argument to the method, which is expected to be an instance of the Bar class. This bar instance is what will be added to the list of bars stored in the class variable all.

**cls.all.append(bar)**: This line of code performs the action of appending the given bar instance to the class variable all. Since cls refers to the class itself, cls.all accesses the class variable all that is meant to store a list of all bar instances. The append() method then adds the bar instance to the end of this list.

This method provides a structured way to manage a collection of Bar instances at the class level, allowing all instances of the class to be easily accessed and manipulated through class methods.

By adding this, the Bar class not only keeps track of each bar instance but also offers a class method show_bar_names to print out the names and music types of all bars.

When we execute the code with our Brooklyn bars:

```
pencil_factory = Bar("Pencil Factory", "Indie Rock")
twinz_lounge = Bar("Twinz Lounge", "Reggae")
jupiter_disco = Bar("Jupiter Disco", "Electronic")
public_records = Bar("Public Records", "Various Live Music")
bar_schimanski = Bar("Bar Schimanski", "Techno")
carousel = Bar("Carousel", "Pop")
westlight = Bar("Westlight", "Jazz")

Bar.show_bar_names()

```

This will output a list of bars along with their music types, providing a good reference for anyone exploring Brooklyn's nightlife and wanting to cut loose and vibe with friends strangers, and anything in between. 

```
Pencil Factory plays Indie Rock music.
Twinz Lounge plays Reggae music.
Jupiter Disco plays Electronic music.
Public Records plays Various Live Music music.
Bar Schimanski plays Techno music.
Carousel plays Pop music.
Westlight plays Jazz music.

```

Hopefully, you learned a thing about Python

P.S.

A nod to my favorite place, but you'll have to visit each place on the list to know which I'm talking about. My challenge to you. Cheers!

{% details summary %} https://www.youtube.com/watch?v=XEjLoHdbVeE {% enddetails %}