Advanced
'''
Iterable
Iterator
Generator
Closures
Decorator
Property
RegEx
Command-Line Arguments (sys)
'''
# Iterable -> objects that implement __iter__ and __next__.The function range() returns an iterator and for loop performs next() on it automatically.
# Python offers a fundamental abstraction called the Iterable.
# An iterable is an object that can be treated as a sequence.
# The object returned by the range function, is an iterable.
filled_dict = {"one": 1, "two": 2, "three": 3}
our_iterable = filled_dict.keys()
print(our_iterable) # => dict_keys(['one', 'two', 'three']). This is an object that implements our Iterable interface.
# We can loop over it.
for i in our_iterable:
print(i) # Prints one, two, three
# However we cannot address elements by index.
our_iterable[1] # Raises a TypeError
# An iterable is an object that knows how to create an iterator.
our_iterator = iter(our_iterable)
# Our iterator is an object that can remember the state as we traverse through it.
# We get the next object with "next()". First object too by next() initially.
print(next(our_iterator)) # => "one"
#alternate syntax
print(our_iterator.__next__()) # => "one"
# It maintains state as we iterate.
next(our_iterator) # => "two"
next(our_iterator) # => "three"
# After the iterator has returned all of its data, it raises a StopIteration exception
next(our_iterator) # Raises StopIteration
# We can also loop over it, in fact, "for" does this implicitly!
our_iterator = iter(our_iterable)
for i in our_iterator:
print(i) # Prints one, two, three
# You can grab all the elements of an iterable or iterator by calling list() on it.
list(our_iterable) # => Returns ["one", "two", "three"]
list(our_iterator) # => Returns [] because state is saved
#We can also create our own iterator using classes that implement __iter__ and __next__
class TopTen:
def __init__(self):
self.num = 1
def __iter__(self):
return self
def __next__(self):
if self.num <= 10: #always specify end
val = self.num
self.num += 1 #increment by 1
return val
else:
raise StopIteration #raise exception or else None is traversed after 10
values = TopTen()
print(next(values)) # => 1
# print(values.__next__())
for i in values:
print(i)
'''
1
2
3
4
5
6
7
8
9
10
'''
# Generators = Functions that create iterators, they create __iter__ and __next__ implicitly
# If a function contains at least one yield statement (it may contain other yield or return statements), it becomes a generator function. Both yield and return will return some value from a function.
# yield = Local variables and theirs states are remembered between successive calls unlike return.
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
'''
>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()
>>> # We can iterate through the items using next().
>>> next(a)
This is printed first
1
>>> # Once the function yields, the function is paused and the control is transferred to the caller.
>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
2
>>> next(a)
This is printed at last
3
>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration
'''
# Closure = The data from the enclosing function gets binded to the inner function and persists even when outer function goes out of scope.
# The criteria that must be met to create closure in Python are summarized in the following points.
# 1. We must have a nested function (function inside a function).
# 2. The nested function must refer to a value defined in the enclosing function.
# 3. The enclosing function must return the nested function.
def print_msg(msg):
# This is the outer enclosing function
def printer():
# This is the nested function
print(msg)
return printer # returns the nested function, not "printer(msg)" call
# Now let's try calling this function.
# Output: Hello
another = print_msg("Hello") # req. to store in another var, since we return a function
another()
# Decorator = A decorator function takes in a function as argument, adds some functionality and returns it. Uses closures ofcourse.
# @ symbol to specify decorators, syntactic sugar
@make_pretty
def ordinary():
print("I am ordinary")
# above is equivalent to
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
# Property
# RegEx (Regular Expressions)
# https://www.programiz.com/python-programming/regex
# Command-Line Arguments
argv # If 'import sys.argv' used
sys.argv # If sys imported as 'import sys'
# $python num.py 9 8 7
sys.argv[0] # num.py
sys.argv[1] # 9
sys.argv[2] # 8
sys.argv[3] # 7