All Things Python (in-so-far as I've been using it!)
Published: 2024-03-15 20:02:28

Hello!

Here marks another phase completed with #flatironschool! I feel as though this phase (phase 3) was quite a different approach to the learning process.

First off - this phase was done in a new language, Python!

Now... At first (And possibly even still...) It's not my favorite of the languages I've been introduced to. That being said - I adore object oriented programming and I thought I'd share some of what I've learned.

But, before we dive into Object-oriented programming - let's mark out something I have enjoyed about Python.

It's clear and concise in its language! If I wanted to consume every cookie in the cookie jar, it's as easy as looping through it like so:

```py
for cookie in cookie_jar:
    consume(cookie)
```
That same could be done in javascript but would look something like...
```js
cookie_jar.forEach((cookie) => {consume(cookie)};
// or
for (let cookie of cookie_jar) { consume(cookie);
```
The javascript is far less readable out the box.

This is just one example.

Printing to the console is straightforward in both. But Python just reads more easily.

```py
print("foo")
```
```js
console.log("foo"
```

Checking the length of an array or string makes more sense to be a wrapper around the object rather than a function OF the object.

```py
len(["1","2","3"])
```
```js
["1","2","3"].length()
```

But I enjoy most of all - classes.

Let's look at an example of a class and break it down line by line.

#### Person.py
```py
1. # lib/person/Person.py
2. from lib.person._Person import PersonMethods
3. 
4. class Person(PersonMethods):
5. 
6.     def __init__(self,name):
7.         self.name = name
8. 
9.     @property
10.     def name(self):
11.         return self._name
12. 
13.     @name.setter
14.     def name(self, name):
15.         if isinstance(name, str) and len(name):
16.             self._name = name
17.         else:
18.             raise ValueError(
19.                 "Name must be a non-empty string"
20.             )
```

##### Breakdown

<u>Line 1</u>: A Single line comment indicating the file path.

<u>Line 2</u>: `from lib.person._Person import PersonMethods` Imports the `PersonMethods` class from the `_Player` module.

<u>Line 4</u>: `class Person(PersonMethods):` Defines a new class named `Person` that inherits from the `PersonMethods` class imported on Line 2. Inheritance allows a class to inherit attributes and methods from another class. In this case, separating methods from Getters / Setters.

<u>Line 6</u>: `__init__` is a `Constructor`. This is a special method that is called when a new instance of the class is instantiated. There are two parameters to the constructor here, `self` and `name`. `self` refers to the instance of the class and is not expressly passed into the constructor upon creation. `name` is a string (in this case) that is passed into the constructor when initializing the instance.

<u>Line 7</u>: `self.name = name` This line assigns the value of `name` that was passed in on line 6 into an attribute of the current instance `self`.

<u>Line 9 - 11</u>: `@property` is a `decorator` used to define a property. Properties provide a way to customize attribute access. For example:
```py
person = Person("John")
print(person.name) # This will print "John"
```
However this is equivalent to calling the `name()` method on the instance.
```py
print(person.name()) # This will also print "John"
```
By using the `@property` decorator, it allows us to more cleanly access the attribute as though it were a direct attribute.

<u>Line 13</u>: `@name.setter` is a decorator that defines a setter method for the `name` property. 

<u>Line 14</u>: `def name(self, name):` defines the `name` method as a setter based on the decorator in line 13.

<u>Line 15</u>: `if isinstance(name, str) and len(name):` This is our first bit of logic! This line checks if the value passed into the setter, `name`, is a non-empty string. `isinstance()` is a function to check if the value is an instance of the `string` class and the `len()` function checks if the string has a length greater than 0

<u>Line 17 - 20</u>: `else: raise ValueError("Name....")` this line only fires if the value passed into the setter method is not a valid string. It will raise a `ValueError` with the message attached.



All in all, by defining a setter method for the name property, the `name` attribute of an instance `Person` can be modified using assignment syntax. For example:
```py
person = Person("John")
person.name = "Alice" # Changes the name of person
```


#### _Person.py
```py
1. # lib/person/_Person.py
2. 
3. class PersonMethods:
4. 
5.     def say_name(self):
6.         return f"My name is {self.name}
```

#### Breakdown

<u>Line 1</u>: A Single line comment indicating the file path.

<u>Line 3</u>: `class PersonMethods:` defines a new class `PersonMethods` that, unlike `Person`, does not inherit any methods. Instead, this class has a single method `say_name()`.

<u>Line 5</u>: `def say_name(self):` definds a method named `say_name` within the method that has the `self` parameter, indicating that this is an instance method (a method specifically used by each instance of the class rather than the class itself). 

<u>Line 6</u>: `return f"My name is {self.name}"` returns a string formatted using string interpolation.

### All together now!

So! By inheriting from `PersonMethods`, instances of the `Person` class gain access to the methods defined in the `PersonMethods` class! For example, the `say_name()` method we created earlier can be invoked directly on instances of the `Person` class!

```py
person = Person("John")
print(person.say_name()) # Output: "My name is John"
```

This illustrates how the `Person` class in `Person.py` benefits from the methods encapsulated in the `PersonMethods` class defined in `_Person.py`!

#### A Caveat to Python...

While I love the object oriented programming paradigm... Python has a major annoyance for my organized brain...

The indentation! 4 spaces!? With Javascript, just wrap everything in curly braces {}. Boom! You know where your scope lines are.

This whitespace sensitivity to define block structure is a real visual problem for me. I appreciate the explicit block delimiters that javascript has with `{}`.

I learned a lot more about Python but this is just a neat little explanation of something I found fun to learn.