sepideh-shamsizadeh / Learning_programing

0 stars 0 forks source link

Python Generators and Unittest #1

Closed sepideh-shamsizadeh closed 1 year ago

sepideh-shamsizadeh commented 1 year ago

Learning advance python

sepideh-shamsizadeh commented 1 year ago

Generators in Python

In Python, a generator is a special type of iterator that allows for lazy, on-demand generation of values. It produces a sequence of values on-the-fly, instead of generating and storing them all at once in memory like a list.

A generator function is a type of Python function that uses the yield keyword to produce a sequence of values one at a time. When a generator function is called, it returns a generator object, which can then be used to produce values by calling its __next__() method.

Here is an example of a simple generator function:

def my_generator():
    yield 1
    yield 2
    yield 3

When you call this generator function, it returns a generator object:

 gen = my_generator()

To produce the values, you can call the next() method of the generator object:

>>> gen.__next__()
1
>>> gen.__next__()
2
>>> gen.__next__()
3

Each time you call __next__(), the generator function picks up where it left off and continues executing until it hits another yield statement, at which point it pauses and returns the value.

One of the key benefits of using a generator is that it can be more memory-efficient than creating a list or other sequence all at once, especially if the sequence is very large or infinite. Additionally, generators are often easier to read and write than equivalent code using traditional loops and lists, especially for cases where you don't need to use the entire sequence at once.

Here are some exercises defined with chatGPT to solve with generators.

  1. Write a generator function range_generator(start, stop, step) that generates the same sequence of values as the built-in range() function. For example, range_generator(0, 10, 2) should produce the values 0, 2, 4, 6, and 8.
  2. Write a generator function fibonacci_generator() that generates an infinite sequence of Fibonacci numbers. For example, fibonacci_generator() should produce the values 0, 1, 1, 2, 3, 5, 8, 13, 21, and so on.
  3. Write a generator function filter_generator(func, iterable) that generates the same sequence of values as the built-in filter() function. For example, filter_generator(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6]) should produce the values 2, 4, and 6.
  4. Write a generator function flatten_generator(iterable) that takes an iterable that may contain nested iterables and generates a flattened sequence of all the values. For example, flatten_generator([1, [2, 3], [4, [5, 6]]]) should produce the values 1, 2, 3, 4, 5, and 6.
  • Flattened sequence: A flattened sequence is a sequence that has been "flattened" into a one-dimensional list or array, where all the elements of the original sequence are placed in a single row, without any nesting or hierarchical structure. For example, consider the following nested list:
    nested_list = [[1, 2], [3, 4, 5], [6, 7, 8, 9]]

To flatten this list, we would need to remove the nested structure and place all the elements in a single row. The flattened sequence for this nested list would be:

    flattened_sequence = [1, 2, 3, 4, 5, 6, 7, 8, 9]

Note that the order of the elements in the flattened sequence is the same as the order in which they appeared in the original nested list.

Flattening a sequence is a common operation in data processing and analysis, especially when dealing with hierarchical data structures like nested lists or dictionaries. It allows us to convert complex data structures into simple, one-dimensional sequences that are easier to work with and analyze.

  1. Write a generator function prime_generator() that generates an infinite sequence of prime numbers. For example, prime_generator() should produce the values 2, 3, 5, 7, 11, 13, 17, 19, 23, and so on.

How writing \'unittest\' for a generator function:

Writing a unit test for a generator function in Python is very similar to writing a unit test for a regular function. The main difference is that you need to test the values generated by the generator function, rather than the return value of the function itself.

Here's an example of a test case for a simple generator function that yields a sequence of numbers:

import unittest

def number_generator(start, stop):
    num = start
    while num < stop:
        yield num
        num += 1

class TestNumberGenerator(unittest.TestCase):
    def test_sequence(self):
        gen = number_generator(1, 5)
        self.assertEqual(list(gen), [1, 2, 3, 4])

In this example, the \'number_generator()'\ function yields a sequence of numbers starting from start and ending at stop. The \'TestNumberGenerator\' test case defines a test method called \'test_sequence()\' that tests whether the generator produces the correct sequence of numbers.

To test the generator, we first create a generator object by calling the \'number_generator()\' function with the desired arguments. We then convert the generator object to a list using the built-in \'list()\' function, which consumes all the values generated by the generator and returns them as a list. Finally, we use the \'assertEqual()\' method to compare the list of values produced by the generator to the expected list.

Note that we need to convert the generator object to a list before making any assertions. This is because once you iterate over a generator, you can't iterate over it again. By converting it to a list, we can consume all the values and make sure that the sequence of values produced by the generator is correct.

sepideh-shamsizadeh commented 1 year ago

Unit test python

Unit testing is a process of testing individual units or components of a software application in isolation to ensure that they are functioning correctly. In Python, the standard library includes a module called \'unittest\', which provides a framework for writing and running unit tests.

The basic idea behind unit testing is to write test cases that verify the correct behavior of small pieces of code, typically functions or methods. A test case is typically a function that exercises the unit under test and checks that the results are as expected. The \'unittest\' module provides a set of assert methods that can be used to check various conditions and raise an exception if the condition is not met.

Here's an example of a simple test case using \'unittest\':

import unittest

def add(x, y):
    return x + y

class TestAddFunction(unittest.TestCase):
    def test_add_integers(self):
        self.assertEqual(add(2, 3), 5)

    def test_add_floats(self):
        self.assertAlmostEqual(add(0.1, 0.2), 0.3, delta=0.0001)

if __name__ == '__main__':
    unittest.main()

In this example, we define a function called \'add()\' that simply adds two values together. We also define a test case called TestAddFunction that inherits from \'unittest\'.TestCase.

The TestAddFunction test case defines two test methods: \'test_add_integers()\' and \'test_add_floats()\'. Each test method calls the \'add()\' function with different inputs and uses the \'assertEqual()\' and \'assertAlmostEqual()\' methods to check that the output is as expected.

To run the tests, we simply call \'unittest.main()\' at the end of the script. This will discover all the test cases in the module and run them.

The \'unittest\' module provides many more features for writing and running tests, including support for fixtures, test suites, and test runners. By writing and running unit tests regularly as part of your development process, you can catch bugs early and ensure that your code is working correctly.