0% found this document useful (0 votes)
33 views81 pages

Unit 3-OOP

The document provides an overview of object-oriented programming (OOP) in Python, detailing key concepts such as classes, instances, and inheritance. It explains how to create classes, instantiate objects, and access their attributes, as well as advanced topics like operator overloading and decorators. Additionally, it covers built-in class attributes, garbage collection, and data hiding.

Uploaded by

gorasiaharshil8
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views81 pages

Unit 3-OOP

The document provides an overview of object-oriented programming (OOP) in Python, detailing key concepts such as classes, instances, and inheritance. It explains how to create classes, instantiate objects, and access their attributes, as well as advanced topics like operator overloading and decorators. Additionally, it covers built-in class attributes, garbage collection, and data hiding.

Uploaded by

gorasiaharshil8
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 81

OBJECT-ORIENTED

PYTHON
Introduction
Python is an object-oriented language.
Due to that creation and usage of class and object is
very much easy.
You can implement any of the OOP concepts using
python.
Overview of OOP Terminology

Class: A user-defined prototype for an object that defines a set of


attributes that characterise any object of the class. The attributes are data
members (class variables and instance variables) and methods, accessed via
dot notation.

Instance: An individual object of a certain class. An object obj that belongs


to a class Circle, for example, is an instance of the class Circle.

Class variable: A variable that is shared by all instances of a class.

Instance variable: A variable that is defined inside a method and


belongs only to the current instance of a class.
Data member: A class variable or instance variable that holds data
associated with a class and its objects.

Instantiation: The creation of an instance of a class.

Function overloading: The assignment of more than one behaviour


to a particular function. The operation performed varies by the types of
objects or arguments involved.

Operator overloading: The assignment of more than one function to


a particular operator.
Creating Classes

The class statement creates a new class definition. The name of the
class immediately follows the keyword class followed by a colon as
follows:

class ClassName:
'Optional class documentation string'
class_suite

The class has a documentation string, which can be accessed via


ClassName.__doc__.
The class_suite consists of all the component statements defining class
members, data attributes and functions.
Example
class MyClass:
variable = "blah"

def function(self):
print("This is a message inside the class.”)
print (MyClass)
print (MyClass.__doc__)

OUTPUT:- Guess??
__main__.MyClass
None
Another example
class Employee:
'Common base class for all employees'
empCount = 0

def __init__(self, name, salary):


self.name = name
self.salary = salary
Employee.empCount += 1

def displayCount(self):
print ("Total Employee %d" % Employee.empCount)

def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)
The variable empCount is a class variable whose value is shared
among all instances of a this class. This can be accessed using an
object of a class from inside the class or outside the class.

The first method __init__() is a special method, which is called class


constructor or initialisation method that Python calls when you
create a new instance of this class.

You declare other class methods like normal functions with the
exception that the first argument to each method is self. Python
adds the self argument to the list for you; you do not need to
include it when you call the methods.
Creating object of a class
You can create an object of a class using any
variable as an object followed by assignment of
class name.
e.g.
myobjectx = MyClass()
emp1= Employee(“abc”,5000)
The variable "myobjectx" holds an object of the
class "MyClass" that contains the variable and the
function defined within the class called "MyClass".
Accessing object variables
To access the variable inside the newly created
object, following way we can do:

myobjectx.variable
Print (myobjectx.variable)

Output:-??
Blah
You can create multiple different objects of class.
However each may contain its own copy of variable defined within
class.
E.g.:-
myobjectx=MyClass()
myobjecty=MyClass()
myobjecty.variable = “ Blah Blah”
print (myobjectx.variable)
print (myobjecty.variable)

Output:-??
Blah
Blah Blah
More on accessing attributes

You can add, remove, or modify attributes of classes and objects at any
time .

emp1.age = 7 # Add an 'age' attribute.


emp1.age = 8 # Modify 'age' attribute.
del emp1.age # Delete 'age' attribute.
Instead of using the normal statements to access attributes,
you can use the following functions −
getattr(obj, name) : to access the attribute of object.
hasattr(obj,name) : to check if an attribute exists or not.
setattr(obj,name,value) : to set an attribute. If attribute
does not exist, then it would be created.
delattr(obj, name) : to delete an attribute.
Ex:
hasattr(emp1, 'age') # Returns true if 'age' attribute exists
getattr(emp1, 'age') # Returns value of 'age' attribute
setattr(emp1, ‘contact_no', 1234) # Set attribute 'age' at 8
delattr(empl, 'age') # Delete attribute 'age'
Built-In Class Attributes

Every Python class keeps following built-in attributes and


they can be accessed using dot operator like any other
attribute −
__dict__: Dictionary containing the class's namespace.
__doc__: Class documentation string or none, if undefined.
__name__: Class name.
__module__: Module name in which the class is defined.
This attribute is "__main__" in interactive mode.
__bases__: A possibly empty tuple containing the base
classes, in the order of their occurrence in the base class list.
Example
class Employee:
'Common base class for all employees'
empCount = 0

def __init__(self, name, salary):


self.name = name
self.salary = salary
Employee.empCount += 1

def displayCount(self):
print ("Total Employee %d" % Employee.empCount)

def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)

print ("Employee.__doc__:", Employee.__doc__)


print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__)
Output:-??

Employee.__doc__: Common base class for all employees


Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {'__module__': '__main__', '__doc__': 'Common
base class for all employees', 'empCount': 0, '__init__': <function
Employee.__init__ at 0x7f02c52a08b0>, 'displayCount': <function
Employee.displayCount at 0x7f02c52a0d30>, 'displayEmployee':
<function Employee.displayEmployee at 0x7f02c52a0dc0>,
'__dict__': <attribute '__dict__' of 'Employee' objects>,
'__weakref__': <attribute '__weakref__' of 'Employee' objects>}
Destroying objects
Python deletes unneeded objects (built-in types or class
instances) automatically to free the memory space.

Garbage Collection by which Python reclaims the blocks of


memory.

It runs during program execution and is triggered when an


object's reference count reaches zero.
a = 40 # Create object <40>
b=a # Increase ref. count of <40>
c = [b] # Increase ref. count of <40>
del a # Decrease ref. count of <40>
b = 100 # Decrease ref. count of <40>
c[0] = -1 # Decrease ref. count of <40>
A class can implement the special method__del__(), called a
destructor, that is invoked when the instance is about to be
destroyed.

This method might be used to clean up any non memory


resources used by an instance.
Example
class Point:
def __init( self, x=0, y=0):
self.x = x
self.y = y
def __del__(self):
class_name = self.__class__.__name__
print (class_name, "destroyed")

pt1 = Point()
pt2 = pt1
pt3 = pt1
print (id(pt1), id(pt2), id(pt3)) # prints the ids of the objects
del pt1
del pt2
del pt3
Output:-??

139636745929872 139636745929872 139636745929872


Point destroyed
Tips
Ideally, you should define your classes in separate file.

You should import them in your main program file using import
statement.
Class Inheritance
You can create a class by deriving it from a pre-existing class
by listing the parent class in parentheses after the new class
name.

The child class inherits the attributes of its parent class.

A child class can override data members and methods from the
parent.
Syntax
Derived classes are declared much like their parent class;
however, a list of base classes to inherit from is given after the
class name −

class SubClassName (ParentClass1[, ParentClass2, ...]):


'Optional class documentation string'
class_suite
Example
class Parent: # define parent class
parentAttr = 100
def __init__(self):
print ("Calling parent constructor")

def parentMethod(self):
print (“Calling parent method“)

def setAttr(self, attr):


Parent.parentAttr = attr

def getAttr(self):
print ("Parent attribute :", Parent.parentAttr)

class Child(Parent): # define child class


def __init__(self):
print ("Calling child constructor")

def childMethod(self):
print (“Calling child method“)
c = Child()
c.childMethod()
c.parentMethod()
c.setAttr(50)
c.getAttr()
print(Child.__bases__)

Output:-??
Calling child constructor
Calling child method
Calling parent method
Parent attribute : 50
(<class '__main__.Parent’>,)
MultiParent Class
You can derive a class from multiple parent classes

class A: # define your class A


.....
class B: # define your class B
.....
class C(A, B): # subclass of A and B
.....
More on inheritance
You can use issubclass() or isinstance() functions to check a
relationships of two classes and instances.

! The issubclass(sub, sup) boolean function returns true if the


given subclass sub is indeed a subclass of the superclass sup.

! The isinstance(obj, Class) boolean function returns true


if obj is an instance of class Class or is an instance of a
subclass of Class
Overriding methods
You can always override your parent class methods.

The good reason about it that you may want to have special or
different functionality in your subclass.
Example
class Parent: # define parent class
def myMethod(self):
print ('Calling parent method')

class Child(Parent): # define child class


def myMethod(self):
print ('Calling child method')

c = Child() # instance of child


c.myMethod() # child calls Child method
________ method
Base overriding methods
Overloading operators
Suppose you have created a Vector class to represent two-
dimensional vectors, what happens when you use the plus
operator to add them?

You could, however, define the __add__ method in your class to


perform vector addition and then the plus operator would
behave as per expectation
Example
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b

def __str__(self):
return ('Vector (%d, %d)' % (self.a, self.b))

def __add__(self, other):


return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2) Output:-??
Vector (7, 8)
Data hiding
An object's attributes may or may not be visible outside the
class definition.

You need to name attributes with a double underscore prefix,


and those attributes then are not be directly visible to outsiders.
Example
class JustCounter:
__secretCount = 0

def count(self):
self.__secretCount += 1
print (self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print (counter.__secretCount)
Output:-??

1
2
Traceback (most recent call last):
File "Class.py", line 91, in <module>
print (counter.__secretCount)
AttributeError: 'JustCounter' object has no attribute
'__secretCount
Python protects those members by internally changing the name
to include the class name.

You can access such attributes as object._className__attrName.

So modified code is
Example
class JustCounter:
__secretCount = 0

def count(self):
self.__secretCount += 1
print (self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print (counter._JustCounter__secretCount)
Output:-??

1
2
2
Decorators
Very powerful and useful tool in Python

Allows programmers to modify the behaviour of function or class

Allow us to wrap another function in order to extend the


behaviour of the wrapped function, without permanently
modifying it

A python decorator is a function that takes in a function, adds


some functionality to it and returns the original function.

In order to understand decorators in details, first we need to


understand First Class Objects
First Class Objects

In Python, functions are first class objects that means that


functions in Python can be used or passed as arguments.
Properties of first class functions:
• A function is an instance of the Object type.

• You can store the function in a variable.

• You can pass the function as a parameter to another


function.
• You can return the function from a function.

• You can store them in data structures such as hash tables,


lists, …
Example 1: Treating the functions as objects.

def shout(text):
return text.upper()
print(shout(“Hello”))
yell = shout
print(yell(“Hello”))

Output: ??
HELLO
HELLO
Example 2: Functions can be passed as arguments
to other functions.
def inc(x):
return x+1
def dec(x):
return x-1
def operate(func,x):
result = func(x)
return result

operate(inc,3)
operate(dec,3)

Output: ??
4
2
Example 3: Functions can return another function.

def is_called(msg):
print("Greeting from is_called.")
def is_returned(): #Clousure function: Are inner fuctions which
remembers the values even if the outer function has completed its
execution.
print("Hello",msg)
return is_returned

New = is_called("World")
New()
Decorators
Functions and methods are called callable as they can be
called.

In fact, any object which implements the special __call__()


method is termed callable.

So in most basic sense, a decorator is a callable that returns a


callable.

Basically, a decorator takes in a function, adds some


functionality and returns it.
Example

def make_pretty(func):
def inner():
print(“I got decorated.”)
func()
return inner

def ordinary():
print(“I am ordinary”)
>>> ordinary()
I am ordinary
>>> #Let’s decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary
Generally, we decorate a function and reassign it as,

ordinary = make_pretty(ordinary)

This is a common construct and for this reason, Python has a


syntax to simplify this.

We can use the @ symbol along with the name of the


decorator function and place it above the definition of the
function to be decorated.
Example

@make_pretty
def ordinary():
print(“I am ordinary”)
is equivalent to

def ordinary():
print(“I am ordinary”)
ordinary = make_pretty(ordinary)
Decorating Functions with Parameters

def divide(a, b):


return a/b

>>> divide(2, 5)
0.4
>>> divide(2, 0)
Traceback (most recent call last):

ZeroDivisionError: division by zero
Decorator for above code
def smart_divide(func):
def inner(a, b):
print(“I am going to divide”, a , “and”, b)
if b ==0:
print(“Whoops! Cannot divide”)
return
return func(a, b)
return inner

@smart_divide
def divide(a, b):
return a/b

>>> divide(2, 5)
I am going to divide 2 and 5
0.4
>>> divide(2, 0)
I am going to divide 2 and 0
Whoops! Cannot divide
General decorator that works with any parameters

This can be achieved by function(*args, **kwargs)

So, general framework is:

def works_for_all(func):
def inner(*args, **kwargs)
print(“I can decorate any function.”)
func(*args, **kwargs)
return inner
Chaining Decorators in Python

def star(func):
def inner(*args, **kwargs):
print(“*” * 30)
func(*args, **kwargs)
print(“*” * 30)
return inner

def percent(func):
def inner(*args, **kwargs):
print(“%” * 30)
func(*args, **kwargs)
print(“%” * 30)
return inner
@star
@percent
def printer(msg):
print(msg)
printer(“Hello”)
Decorator for above code
Output: ??

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
The above syntax is:
@star
@percent
def printer(msg):
print(msg)
printer = star(percent(printer))
Descriptor

Descriptors give us a powerful technique to write reusable code


that can be shared between classes

To manage the attributes of different classes which use the object


as reference.

In descriptors, three different methods __getters__(),


__setters__() and __delete__() are used.

If any of these methods are defined for an object, it can be


termed as a descriptor.

A descriptor is a mechanism behind properties, methods, static


methods, class methods and super().
Descriptor Protocol
A descriptor is an object attribute with binding behaviour

In other language descriptors are getter setter, where public


functions are defined to get and set a private variable.

Python doesn’t have a private variable concepts and descriptor


protocols are Pythonic way of achieving similar to that.

Descriptors are a new way to implement classes in Python, and


it does not need to inherit anything from a particular object.

There are three protocol in python descriptors for setters,


getters and delete method.
Descriptor Protocol (Conti…)
gfg.__get__(self, obj, type=None): This attribute is called when you
want to retrieve the information (value = obj.attr), and whatever it
returns is what will be given to the code that requested the attribute’s
value.

gfg.__set__(self, obj, value): This method is called to set the value of


an attribute (obj.attr = ‘value’), and it will not return anything to you.

gfg.__delete__(self, obj): This method is called when the attribute is


deleted from the object (del obj.attr)

Instance above returns to the object where the attribute was


accessed, and the owner is the class where the descriptor was
assigned as an attribute.
Invoking descriptor
Descriptors are invoked automatically whenever it receives the call
for a set() or get() method.

For example, obj.gfg looks up gfg in the dictionary of obj. If gfg


defines the method __get__(), then gfg.__get__(obj) is invoked. It can
also directly be invoked by method name i.e. gfg.__get__(obj).

Example:-
def __getattribute__(self, key):
v = object.__getattribute__(self, key)
if hasattr(v, ‘__get__’):
return v.__get__(None, self)
return v
The important points to remember are:

Descriptors are invoked by the __getattribute__() method.

Overriding __getattribute__() prevents automatic descriptor calls.

object.__getattribute__() and type.__getattribute__() make different


calls to __get__().

Data descriptors always override instance dictionaries.

Non-data descriptors may be overridden by instance dictionaries.


Example
Class Descriptor(object):
def __init__(self, name=‘ ‘):
self.name = name
def __get__(self, obj, objtype):
return “{}for{}”.format(self.name, self.name)
def __set__(self, obj, name):
if isinstance(name, str):
self.name = name
else:
raise TypeError(“Name should be string”)
Class GFG(object):
name = Descriptor()
g = GFG()
g.Name = “Python”
print(g.name)

Output:??
PythonforPython
Simple class example
class Car:
fuel_cap = 90
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
print(“Inside Car Init method”)
self.fuel_cap = fuel_cap
def __str__(self):
return "{0} model {1} with a fuel capacity of {2}
ltr.".format(self.make,self.model,self.fuel_cap)

car1 = Car("BMW","X7",40)
print(car1)

Output:??
BMW model with X7 with a fuel capacity of 40 ltr.
Example
class Descriptor:
def __init__(self):
self.__fuel_cap = 0
def __get__(self, instance, owner):
return self.__fuel_cap
def __set__(self, instance, value):
if isinstance(value, int):
print(value)
else:
raise TypeError("Fuel Capacity can only be an integer")
if value < 0:
raise ValueError("Fuel Capacity can never be less than zero")
self.__fuel_cap = value
def __delete__(self, instance):
del self.__fuel_cap
Example (Conti…)
class Car:
fuel_cap = Descriptor()
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
def __str__(self):
return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make, self.model,
self.fuel_cap)

car2 = Car("BMW","X7",40)
print(car2)

Output:??
40
BMW model with X7 with a fuel capacity of 40 ltr.
Multithreading
Multiple threads within a process share the same data space with the
main thread and can therefore share information or communicate with
each other more easily than if they were separate processes.

Threads sometimes called light-weight processes and they do not


require much memory overhead; they are cheaper than processes.

It can be pre-empted (interrupted).

It can temporarily be put on hold (also known as sleeping) while other


threads are running - this is called yielding.
Multithreading (Conti…)
Starting a New Thread
_thread.start_new_thread(function, args[, kwargs])

The method call returns immediately and the child thread starts
and calls function with the passed list of args. When function
returns, the thread terminates.

Here, args is a tuple of arguments; use an empty tuple to call


function without passing any arguments. kwargs is an optional
dictionary of keyword arguments.
Example
import _thread
import time

#define a function for the thread


def print_name(threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print (“%s: %s” % (threadName, time.ctime(time.time())))

#Create two threads as follows


try:
_thread.start_new_thread( print_name, (“Thread-1”, 2, ))
_thread.start_new_thread( print_name, (“Thread-2”, 4, ))
except:
print (“Error: unable to start the thread”)
While 1:
pass
The Threading Module
The threading module exposes all the methods of the thread module
and provides some additional methods:-

threading.activeCount() − Returns the number of thread objects


that are active.
threading.currentThread() − Returns the number of thread
objects in the caller's thread control.
threading.enumerate() − Returns a list of all thread objects that
are currently active.

In addition to the methods, the threading module has the Thread class
that implements threading.
The methods provided by the Thread class are as follows:-
run() − The run() method is the entry point for a thread.
start() − The start() method starts a thread by calling the
run method.
join([time]) − The join() waits for threads to terminate.
Is_alive() − The is_alive() method checks whether a thread
is still executing.
getName() − The getName() method returns the name of a
thread.
setName() − The setName() method sets the name of a
thread.
Creating Thread Using Threading Module

To implement a new thread using the threading module, you have to


do the following −
Define a new subclass of the Thread class.
Override the __init__(self [,args]) method to add additional
arguments.
Then, override the run(self [,args]) method to implement what the
thread should do when started.

Once you have created the new Thread subclass, you can create an
instance of it and then start a new thread by invoking the start(),
which in turn calls the run() method.
Example
Multithreading.txt
Metaclass

A metaclass in Python is a class of a class that defines how a class


behaves.

A class is itself an instance of a metaclass.

A class in Python defines how the instance of the class will behave.

In order to understand metaclasses well, one needs to have prior


experience working with Python classes.

This thing we can achieve with the help of “type”.

A metaclass in python is an instance of class “type”.


Creating class dynamically using ‘type’

Syntax:
type(classname, superclasses, attributes_dict)
classname = Any meaningful classname
superclasses = Tuple of base classes(in case of inheritance)
attributes_dict = dictionary of attributes as body of class
Example:
Foo = type(‘Foo’, (), {})
x = Foo()
print (x)
print (Foo())

Output:-??
<__main__.Foo object at 0x7f57b3d8d580>
Example (Conti…)
Foo = type(‘Foo’, (), {‘subject’: ‘python’, ‘instructor’: ‘teacher’})
x = Foo()
Output:-??
print(x.subject,x.instructor) python teacher
print(x) <__main__.Foo object at 0x7f1d1ec09580>
print(Foo) <class '__main__.Foo'>

In case if we want to inherit from Foo() class, then

Bar = type(‘Bar’, (Foo,), dict(attr=100))


x = Bar()
print(x.attr) Output:-??
print(x.__class__) 100
<class '__main__.Bar'>
print(x.__class__.__bases__)
(<class '__main__.Foo'>,)
Creating Custom Metaclasses
In Python, we can customize the class creation process by passing the
metaclass keyword in the class definition.
This can also be done by inhering a class that has already passed in this
keyword.
Example:
Class MyMeta(type):
pass
Class MyClass(metaclass=MyMeta)
pass
Class MySubclass(MyClass):
pass
print(type(MyMeta))
Output:-??
print(type(MyClass)) <class ‘type’>
print(type(MySubclass)) <class ‘__main__.MyMeta’>
<class ‘__main__.MyMeta’>
__new__ and __init__
__new__ is used when one wants to define dict or bases tuples
before the class is created.

The return value of __new__ is usually an instance of cls.

__new__ allows subclasses of immutable types to customize instance


creation.

It can be overridden in custom metaclasses to customize class creation.

__init__ is usually called after the object has been created so as to


initialize it.
Syntax
class MetaOne(type):
def __new__(cls, name, bases, dict):
pass

class MetaTwo(type):
def __init__(self, name, bases, dict):
pass
Example
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, superclasses, attributedict):
print("clsname:", clsname)
print("superclasses:", superclasses)
print("attrdict:", attributedict)
return super(MetaCls, cls).__new__(cls, \
clsname, superclasses, attributedict)

C = MetaCls('C', (object, ), {})


print("class type:", type(C))

Output:??
clsname: C
Superclasses: (<class ‘object’>, )
Attrdict: { }
class type: <class ‘__main__.MetaCls’>
Metaclass Inheritance
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):

return super(MetaCls, cls).__new__(cls, clsname, supercls, attrdict)

C = MetaCls('C', (object, ), {})

## class A inherits from MetaCls


class A(C):
pass

print(type(A))

Output:??
<class ‘__main__.MetaCls’>
Iterators
Iterators are objects that can be iterated upon.
Python iterator object must implement two special
methods, __iter__()and __next__(), collectively
called the iterator protocol.
An object is called iterable if we can get an iterator
from it. Most of built-in containers in Python
like: list, tuple, dictionary, set, string etc. are
iterables.
The iter() function (which in turn calls
the __iter__() method) returns an iterator from them.
# define a list
my_list = [4, 7, 0, 3]
# get an iterator using iter()
my_iter = iter(my_list)
## iterate through it using next()
#prints 4
print(next(my_iter))
#prints 7
print(next(my_iter))
## next(obj) is same as obj.__next__()
#prints 0
print(my_iter.__next__())
#prints 3
print(my_iter.__next__())
## This will raise error, no items left
next(my_iter)
class yrange:
def __init__(self, n):
self.i = 0
self.n = n
def __iter__(self):
return self
def next(self):
if self.i < self.n:
i = self.i
self.i += 1
return i
else:
raise StopIteration()
>>> y = yrange(3)
>>> y.next() 0
>>> y.next() 1
>>> y.next() 2
>>> y.next() Traceback (most recent call last): File
"<stdin>", line 1, in <module> File "<stdin>", line
14, in next StopIteration

You might also like