Python Fundamentals

Concepts and methods on the fundamentals of Python

Python Fundamentals

Concepts and methods on the fundamentals of Python.

Modules & Methods

Modules contain a “library” of methods

Object Oriented Programming (OOP)

Classes

Consists of attributes and methods

Example: Car

Attributes: fuel, max speed, color

Method (function): refuel(), setSpeed()

Methods create attributes for classes

Creating a class

class MyClass:
    x = 55
    name = "Allie Trent"
    attr_list = ["Bones", "Food", "Frisbee"]
MyClass.name
'Allie Trent'

Methods in a class

class Methods_Class:
    x = "Hello World"

    def my_method():
        print("Contents of my_method")

    def subtractor(num_one, num_two):
        product = num_one - num_two
        print("{} - {} = {}".format(num_two, num_one, product))

    def print_stuff(a, b):
        print(a, b)
print(Methods_Class)
print(Methods_Class.x)
print(Methods_Class.my_method)
print(Methods_Class.my_method())
print(Methods_Class.subtractor(20, 5))
print(Methods_Class.print_stuff(3, "Hello"))
<class '__main__.Methods_Class'>
Hello World
<function Methods_Class.my_method at 0xffffab79b400>
Contents of my_method
None
5 - 20 = 15
None
3 Hello
None

__init__

Function defined at the start of a class

class Animals:
    def __init__(self, size, noise, color, num_of_legs):
        self.size = size
        self.noise = noise
        self.color = color
        self.num_of_legs = num_of_legs
dog = Animals("Large", "Woof", "Liver & White", 4)
dog
<__main__.Animals at 0xffffab81e590>
dog.noise
'Woof'

Using attributes in a method

class Animals:
    def __init__(self, species, name, noise, color):
        self.species = species
        self.name = name
        self.noise = noise
        self.color = color

    def describe(self):
        print(
            "{} is {} which makes a {} noise".format(self.name, self.color, self.noise)
        )
dog = Animals("Dog", "Allie", "Woof", "Liver & White")
cat = Animals("Cat", "Misty", "Meow", "Black")
dog.describe()
Allie is Liver & White which makes a Woof noise

Changing variable names in a class object

class Ages:
    def __init__(self, age):
        self.age = age

    def plus_year(self):
        self.age += 1

    def show_age(self):
        print("Your age is {}".format(self.age))
me = Ages(27)
me.plus_year()
me.show_age()
Your age is 28
class Warrior:
    def __init__(self, name, strength, health):
        self.name = name
        self.strength = strength
        self.health = health

    def report(self):
        print(
            "Hi {}, your strength is {} and health is {}".format(
                self.name, self.strength, self.health
            )
        )

    def heal(self):
        self.health += 1

    def damage(self):
        self.health -= 1

    def workout(self):
        self.strength += 1
character = Warrior("Geocoug", 60, 100)
[character.damage() for i in range(8)]
character.report()
[character.heal() for i in range(5)]
character.report()
character.workout()
character.report()
Hi Geocoug, your strength is 60 and health is 92
Hi Geocoug, your strength is 60 and health is 97
Hi Geocoug, your strength is 61 and health is 97

Practical Example - PayFriend

Create an online bank where: - User can create a new account that includes: Account type (currnet, savings, etc), name of account holder, account balance, etc. - User can withdraw or deposit money, or check balance

class Banking:
    def __init__(self, name, account, balance):
        self.name = name
        self.account = account
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        self.balance -= amount

    def report(self):
        print(
            "{}, the balance of {} is ${}".format(self.name, self.account, self.balance)
        )
my_account = Banking("Geocoug", "Savings", 1000)
my_account.deposit(50)
my_account.report()
my_account.withdraw(500)
my_account.report()
Geocoug, the balance of Savings is $1050
Geocoug, the balance of Savings is $550

Functional Programming

Lambda

Quicker way of creating functions

def some_func(x):
    y = x + 2


lambda x: x + 2
<function __main__.<lambda>(x)>
lambda_func = lambda x: x + 2
lambda_func(6)
8
other_func = lambda name: "Hello there, {}".format(name)
other_func("Geocoug")
'Hello there, Geocoug'
another_func = lambda x, y: x + y
another_func(3, 4)
7

Map

Apply the same function/operation on all elements in a list

map(name of function, name of list)

# using map as a normal function
somelist = [1, 10, 20, 15]


def divider(x):
    y = x / 5
    return y


newlist = list(map(divider, somelist))
newlist
[0.2, 2.0, 4.0, 3.0]
somelist = [1, 10, 20, 15]
newlist = list(map(lambda x: x / 5, somelist))
newlist
[0.2, 2.0, 4.0, 3.0]
num_tuple = (1, 10, 20, 15)
new_tuple = tuple(map(lambda x: x / 5, num_tuple))
new_tuple
(0.2, 2.0, 4.0, 3.0)
list_one = [1, 2, 3, 4]
list_two = [90, 80, 70, 60]

new_list = map(lambda x, y: x + y, list_one, list_two)
list(new_list)
[91, 82, 73, 64]

Filter

my_list = [1, 14, 53, 72, 22, 99]
filtered_list = filter(lambda x: x >= 50, my_list)
list(filtered_list)
[53, 72, 99]

Generators

Generators - yield instead of return. Return terminates local variables, yield ‘pauses’.

See - How do python generators work?

def a():
    x = 5
    yield x


a()
<generator object a at 0xffffab9637d0>
def a():
    x = 5
    yield x
    x += 1
    yield x


example = a()
print(next(example))
print(next(example))
print(next(example))
5
6
StopIteration: 
def generator_fn(x):
    for i in range(x):
        yield i
import sys

sys.getsizeof(example)  # bytes
104
g = generator_fn(100_000_000)
dir(g)
['__class__',
 '__del__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__lt__',
 '__name__',
 '__ne__',
 '__new__',
 '__next__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'gi_yieldfrom',
 'send',
 'throw']
next(g)
0
next(g)
1
next(g)
2
help(g)
Help on generator object:

generator_fn = class generator(object)
 |  Methods defined here:
 |  
 |  __del__(...)
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  close(...)
 |      close() -> raise GeneratorExit inside generator.
 |  
 |  send(...)
 |      send(arg) -> send 'arg' into generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  throw(...)
 |      throw(value)
 |      throw(type[,value[,tb]])
 |      
 |      Raise exception in generator, return next yielded value or raise
 |      StopIteration.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  gi_code
 |  
 |  gi_frame
 |  
 |  gi_running
 |  
 |  gi_yieldfrom
 |      object being iterated by yield from, or None

Nested Generators

def inner():
    print("We're inside")
    value = yield 2
    print("Received", value)
    return 4


def outer():
    yield 1
    retval = yield from inner()
    print("Returned", retval)
    yield 5
g = outer()
next(g)
1
next(g)
We're inside
2
g.send(3)
Received 3
Returned 4
5

Generator vs. List Comprehension

import time
n = 100_000_000
start = time.time()
x = [i * i for i in range(n)]
end = time.time()
print(type(x))
print("{:.4f}".format(end - start))
<class 'list'>
4.3685
start = time.time()
g = (i * i for i in range(n))
end = time.time()
print(type(g))
print("{:.4f}".format(end - start))
<class 'generator'>
0.0001