Advanced Python (Fall 2017)/lecture3

From EIK wiki

Lecture 3

More scope (classes)

NB! Even though classes in python work as a namespace with their own scope, you should use modules (i.e .py files) instead of classes for this purpose.

Classes in python create their own scope. Symbols defined outside the class are available from inside the class.

MY_CONSTANT = 'constant outside of the class'

class MyClass():
    print(MY_CONSTANT)
    print('lines in my_class are executed')

    def my_function():
        print('my_function called')

    my_function()

this prints

constant outside of the class
lines in my_class are executed
my_function called

NB! Even though classes in python work as a namespace with their own scope, you should use modules (i.e .py files) instead of classes for this purpose.

Symbols defined in the class are available outside the class using the class name as a namespace

class A:
    my_variable = 'hi'

    def my_function():
        print('my_function called')


print(A.my_variable)
A.my_function()

NB! Even though classes in python work as a namespace with their own scope, you should use modules (i.e .py files) instead of classes for this purpose.

Classes in python should be used as a constructor for objects. like in this example. An object is created when the class is called.

methods in a class take a first argument called 'self'. This argument is added implicitly when the method is called, but needs to be added explicitly when the method is defined. 'self' refers to the instance (i.e the new object created when the class is called). See: https://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods

class MyClass():
    A = 'class variable'

    def __init__(self, c):
        self.c = c  # instance variable

    def my_method(self):
        """
        methods can only be called on the instance of the class.
        So this will work:
        `MyClass(4).my_method()  # prints 4`
        But this will not work:
        `MyClass.my_method()  # raises Error`

        self refers to the instance. The instance has the attributes of the class including:

        - class variables (i.e `self.A`)
        - class methods  (i.e. `self.my_class_method()`)
        - static methods (i.e. `self.my_static_method()`)

        and self also has attributes of the instance including:

        instance variables (i.e. `self.c`)
        instanct methods (i.e. `self.my_method`)
        """
        print(self.c)

    @classmethod
    def my_class_method(cls):
        """
        cls refers to the class. It has attributes of the class, including:
        - class variables (i.e `self.A`)
        - class methods  (i.e. `self.my_class_method()`)
        - static methods (i.e. `self.my_static_method()`)
        """
        print(cls.A)

    @staticmethod
    def my_static_method():
        """
        static methods work like a regular function that is not in a class.
        They do not have access to variables and methods in the class or the instance.
        They are only in the class for organizational reasons, and may be better
        outside the class.
        """
        print('static method called')


print(MyClass.A)  # prints 'class variable'
MyClass.my_class_method()  # prints 'class variable'
MyClass.my_static_method()  # prints 'static method called'

my_instance = MyClass('my instance variable')
my_instance.my_class_method()  # prints 'class variable'
print(my_instance.c)  # prints 'my instance variable'
my_instance.my_method()  # prints 'my instance variable'
my_instance.my_static_method()  # prints 'static method called'

Generators

First we must learn about while loops. For iterating and counting, for is superior. They are simple and usually only used in the following way:

while True:
    do_something()
    if some_condition:
        break

Task 1

NOTE: you most likely will not need to write generators in your python code. iterate through a list using a while and print every element

"""
You should use a `for` loop instead in real world situations.
This is just for teaching about `while`
"""
MY_NAMES = ['hi', 'bye', 'peep']

for i in MY_NAMES:
    print(i)

i = 0
while i < len(MY_NAMES):
    print(MY_NAMES[i])
    i += 1

A generator is a special object in python that can be iterated over, but each item in the iteration is generated lazily. That means it does not know the next element until it has been evaluated.

yield is similar to return, as in the value yielded is used the next item in the generator. But yield does not stop the execution of the function.

Here are some examples:

def my_generator_1():
    yield 1
    yield 2
    yield 3


for i in my_generator_1():
    print(i)

# 1
# 2
# 3

print(list(my_generator_1()))
# [1, 2, 3]


def my_generator_2():
    for i in range(10, 0, -1):
        yield i


print(list(my_generator_2()))
# [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Task 2

implement a range-like function using a generator

def my_function(first, second=None, third=1):
    """
    NOTE: this implementation of range fails on negative step values
    """
    if second:
        stop = second
        start = first
    else:
        stop = first
        start = 0
    step = third
    i = start
    while i < stop:
        yield i
        i += step