14classes, Modules, Packages
14classes, Modules, Packages
This is the reason that built-in functions like id(), print() etc. are
always available to us from any part of the program.
Each module creates its own global namespace.
These different namespaces are isolated. Hence, the same name
that may exist in different modules do not collide.
Modules can have various functions and classes. A local
namespace is created when a function is called, which has all the
names defined in it. Similar, is the case with class. Following
diagram may help to clarify this concept.
https://www.programiz.com/python-programming/namespace
3
Baidya Nath Saha
Namespace in Python
Namespaces are one honking great idea -- let's do more of those!
https://www.programiz.com/python-programming/namespace
4
Design problem
5
Create a class with methods
6
Difference between Abstraction
and Encapsulation
Hide
details Hide details
in design in
level implementation
level
7
Class
8
Class
9
Public, Private and Protected Variables
10
Public, Private and Protected Variables
11
Example: Public Attributes
12
Example: Protected Attributes
class employee:
def __init__(self, name, sal):
self._name=name # protected attribute
self._salary=sal # protected attribute
e1=employee(“Swati",10000)
Python's convention to make an
instance variable protected is to
print(e1._salary)
add a prefix _ (single underscore)
e1._salary=20000
to it. This effectively prevents it
print(e1._salary)
to be accessed, unless it is from
within a sub-class.
13
Example: Protected Attributes
14
Example: Private Attributes
class employee:
def __init__(self, name, sal):
self.__name=name # private attribute
self.__salary=sal # private attribute
e1=employee(“Bill",10000)
Similarly, a double underscore __
prefixed to a variable makes
print(e1.__salary)
it private. It gives a strong
suggestion not to touch it from
AttributeError: 'employee' object
outside the class. Any attempt to
has no attribute '__salary'
do so will result in an Attribute
Error:
15
Private variables
16
Private variables : Name mangling
E1=employee(“Bill",10000)
print(e1._employ__salary
e1._employee__salary=20000
print(e1._employee__salary)
17
Private variables: Name mangling
18
Private variables: Name mangling
Name mangling is helpful for letting subclasses override methods
without breaking intraclass method calls. For example:
class Mapping: class MappingSubclass(Mapping):
def __init__(self, iterable): def update(self, keys, values):
self.items_list = [] # provides new signature for
self.__update(iterable) update()
def update(self, iterable): # but does not break __init__()
for item in iterable: for item in (keys, values): s
self.items_list.append(item) self.items_list.append(item)
__update = update
# private copy of original update() method
19
Private variables: Name mangling
20
Private variables: Name mangling
21
Odds and Ends
22
Odds and Ends
A piece of Python code that expects a particular abstract
data type can often be passed a class that emulates the
methods of that data type instead. For instance, if you have
a function that formats some data from a file object, you can
define a class with methods read() and readline() that get the
data from a string buffer instead, and pass it as an argument.
23
Iterators
By now you have probably noticed that most container
objects can be looped over using a for statement:
24
Iterators
25
Iterators
>>> s = 'abc‘
>>> it = iter (s)
>>> it Having seen the mechanics behind
<iterator object at 0x00A1DB50> the iterator protocol, it is easy to
>>> next(it) add iterator behavior to your
'a'
>>> next(it)
classes. Define an __iter__()
'b' method which returns an object
>>> next(it) with a __next__() method. If the
'c' class defines __next__(), then
>>> next (it) __iter__() can just return self:
Traceback (most recent call last):
File “<stdin>” , line 1, in <module>
next(it)
StopIteration
26
Iterators
class Reverse: >>> rev = Reverse('spam')
"""Iterator for looping over a sequence >>> iter (rev)
backwards."""
<__main__.Reverse object at
def __init__(self, data):
0x00A1DB50>
self.data = data
self.index = len(data) >>> for char in rev:
def __iter__(self): ... Print (char)
return self
...
def __next__(self):
if self.index == 0: m
raise StopIteration a
self.index = self.index - 1 p
return self.data[self.index]
s
27
Generators
Generators are a simple and powerful tool for creating iterators.
They are written like regular functions but use the yield
statement whenever they want to return data. Each time next()
is called on it, the generator resumes where it left off (it
remembers all the data values and which statement was last
executed). An example shows that generators can be trivially
easy to create: >>> for char in reverse('golf'):
... print (char)
def reverse(data):
...
for index in range(len(data)-1, -1, -1): f
yield data[index] l
o
g
28
Generators
Anything that can be done with generators can also be done with
class-based iterators as described in the previous section. What
makes generators so compact is that the __iter__() and __next__()
methods are created automatically.
Another key feature is that the local variables and execution state
are automatically saved between calls. This made the function
easier to write and much more clear than an approach using
instance variables like self.index and self.data.
In addition to automatic method creation and saving program state,
when generators terminate, they automatically raise StopIteration.
In combination, these features make it easy to create iterators with
no more effort than writing a regular function.
29
Generator Expressions
Some simple generators can be coded succinctly as expressions using
a syntax similar to list comprehensions but with parentheses instead
of square brackets. These expressions are designed for situations
where the generator is used right away by an enclosing function.
Generator expressions are more compact but less versatile than full
generator definitions and tend to be more memory friendly than
equivalent list comprehensions.
Examples:
30
Generator Expressions
31