This is a part of the series of posts on Python Crash Course. Having seen the major parts of the core language, let us now look into some of additional frills that make code a lot easier.
In a previous sections, we saw loops on lists, tuples, etc.
for element in (1, 2, 3): print(element)
Python does not limit these iterations to its built-in collection types. Any class that implements the required methods can be used as an iterable. For example, you want to iterate over the first n elements of Fibonacci series, you can create a simple iterator for that:
class Fib: def __init__(self, n): self.max = n self.last = 0 self.secondlast = 0 def __iter__(self): return self def __next__(self): if self.last: self.secondlast, self.last = self.last, self.last + self.secondlast else: self.last = 1 if self.last > self.max: raise StopIteration return self.last for x in Fib(100): print(x)
In fact, you need not implement the next method in the object that you are working with. All you need is the iter() method. This method should return an object which implements the next()
def fib(n): i, j = 0, 1 while j <= n: yield(j) i, j = j, i+j for x in Fib(100): print(x)
This is all you need! Note that keyword 'yield'. It is not return. The method does not return anything. It yields one value at a time. Of course, internally this translates to an iterator of some sort. But, the code certainly looks much better and much easier to manage. Since we have lesser script, it means there is lesser effort on interpretation and more of the C code doing the job. Naturally the performance is better.
The folks who made Python were not satisfied with simple Generators. They wanted to go one step further. Why do we have to create a new class or method for something that can be done by just one line of code? Generator expressions do just that! Naturally they are not as versatile as iterators and generators. But there are times when we really do not need all that.
For example, if you want the list of first 10 cubes, you just need a single line of code!
print([x**3 for x in range(10)])
Everything in Python is an object - so is an exception. Any exception is an instance of a class that extends the common base class - Exception. You can 'raise' an exception, using an object of the Exception class. Or else, you can just give the class name as a parameter to the 'raise' command. Python will take care of creating an object for it.
class B(Exception): pass class C(B): pass class D(C): pass for cls in [B, C, D]: try: raise cls() except D: print("D") except C: print("C") except B: print("B")
Note that if the except clauses were reversed (with except B first), it would have printed B, B, B - the first matching except clause is triggered. The concept of Exceptions is not new in Python. Exceptions have been used in several other languages in the past, and most developers are very familiar with them. But the interesting twist that Python provides is because of the flexibility of Python classes and objects. Now you can pass in any damn information with any exception. All you need to do is to create the instance of the exception object, set the object attributes and then raise it!
try: e = Exception('Additional information') e.more_info = 'Some more information' raise e except Exception as e: print(type(e)) print(e.args) print(e.more_info)
This prints the type of the Exception (Exception), followed by a tuple containing the one argument that was passed in while creating the exception. You can have multiple arguments there. Next line prints 'Some more information' about the exception. This opens infinite possibilities for passing data from the exception to the catch block. You can send out not just strings, but any object that could be useful to the catch block.
Such minor flexibilities in Python open up infinite possibilities when you design and code!