Thursday, March 17, 2022

Understanding OOP principles

OOP is a way of bundling properties and behavior into a single entity, which we call objects. To make this bundling more efficient and modular, there are several principles available in Python, outlined as follows:

• Encapsulation of data

• Inheritance

• Polymorphism

• Abstraction

We will study each of these principles in detail.

Encapsulation of data

Encapsulation is a fundamental concept in OOP and is also sometimes referred to as abstraction. But in reality, the encapsulation is more than the abstraction. In OOP, bundling of data and the actions associated with the data into a single unit is known as encapsulation. Encapsulation is actually more than just bundling data and the associated actions. We can enumerate three main objectives of encapsulation here, as follows:

• Encompass data and associated actions in a single unit.

• Hide the internal structure and implementation details of the object.

• Restrict access to certain components (attributes or methods) of the object.

Encapsulation simplifies the use of the objects without knowing internal details on how it is implemented, and it also helps to control updates to the state of the object.

Let us discuss these objectives in detail.

Encompassing data and actions

To encompass data and actions in one init, we define attributes and methods in a class. A class in Python can have the following types of elements:

• Constructor and destructor

• Class methods and attributes

• Instance methods and attributes

• Nested classes

We have discussed these class elements already in the previous section, except nested or inner classes. We already provided the Python code examples to illustrate the implementation of constructors and destructors. We have used instance attributes to encapsulate data in our instances or objects. We have also discussed the class methods, static methods, and class attributes with code examples in the previous section.

To complete the topic, we will discuss the following Python code snippet with a nested class. Let's take an example of our Car class and an Engine inner class within it. Every car needs an engine, so it makes sense to make it a nested or inner class:

#carwithinnerexample1.py

class Car:

"""outer class"""

c_mileage_units = "Mi"

def __init__(self, color, miles, eng_size):

self.i_color = color

self.i_mileage = miles

self.i_engine = self.Engine(eng_size)

def __str__(self):

return f"car with color {self.i_color}, mileage \

{self.i_mileage} and engine of {self.i_engine}"

class Engine:

"""inner class"""

def __init__(self, size):

self.i_size = size

def __str__(self):

return self.i_size

if __name__ == "__main__":

car = Car ("blue", 1000, "2.5L")

print(car)

print(car.i_engine.i_size)

In this example, we defined an Engine inner class inside our regular Car class. The Engine class has only one attribute—i_size, the constructor method (__init__), and the __str__ method. For the Car class, we updated the following as compared to our previous examples:

• The __init__ method includes a new attribute for engine size, and a new line has been added to create a new instance of Engine associated with the Car instance.

• The __str__ method of the Car class includes the i_size inner class attributes in it.

The main program is using a print statement on the Car instance and also has a line to print the value of the i_size attribute of the Engine class. The console output of this program will be similar to what is shown here:

car with color blue, mileage 1000 and engine of 2.5L

2.5L

The console output of the main program shows that we have access to the inner class from within the class implementation and we can access the inner class attributes from outside.

In the next post, we will discuss how we can hide some of the attributes and methods to not be accessible or visible from outside the class.

Share:

Wednesday, March 16, 2022

Special methods

When we define a class in Python and try to print one of its instances using a print statement, we will get a string containing the class name and the reference of the object instance, which is the object's memory address. There is no default implementation of the to string functionality available with an instance or object. The code snippet showing this behavior is presented here:

#carexampl4.py

class Car:

def __init__(self, color, miles):

self.i_color = color

self.i_mileage = miles

if __name__ == "__main__":

car = Car ("blue", 1000)

print (car)

We will get console output similar to the following, which is not what is expected from a print statement:

<__main__.Car object at 0x100caae80>

To get something meaningful from a print statement, we need to implement a special __str__ method that will return a string with information about the instance and that can be customized as needed. Here is a code snippet showing the carexample4.py file with the __str__ method:

#carexample4.py

class Car:

c_mileage_units = "Mi"

def __init__(self, color, miles):

self.i_color = color

self.i_mileage = miles

def __str__(self):

return f"car with color {self.i_color} and \

mileage {self.i_mileage}"

if __name__ == "__main__":

car = Car ("blue", 1000)

print (car)

And the console output of the print statement is shown here:

car with color blue and mileage 1000

With a proper __str__ implementation, we can use a print statement without implementing special functions such as to_string(). It is the Pythonic way to control the string conversion. Another popular method used for similar reasons is __repr__,which is used by a Python interpreter for inspecting an object. The __repr__ method is more for debugging purposes.

These methods (and a few more) are called special methods or dunders, as they always start and end with double underscores. Normal methods should not use this convention.

These methods are also known as magic methods in some literature, but it is not the official terminology. There are several dozen special methods available for implementation with a class. A comprehensive list of special methods is available with the official Python 3 documentation at https://docs.python.org/3/reference/datamodel.html#specialnames.

In the next posts, we will study different object-oriented principles available in Python.

Share:

Tuesday, March 15, 2022

Distinguishing between class methods and instance methods

 In Python, we can define three types of methods in a class, which are:

• Instance methods: They are associated with an instance and need an instance to be created first before executing them. They accept the first attribute as a reference to the instance (self) and can read and update the state of the instance. __init__,which is a constructor method, is an example of an instance method.

• Class methods: These methods are declared with the @classmethod decorator. These methods don't need a class instance for execution. For this method, the class reference (cls is used as a convention) will be automatically sent as the first argument.

• Static methods: These methods are declared with the @staticmethod decorator. They don't have access to cls or self objects. Static methods are like utility functions that take certain arguments and provide the output based on the arguments' values—for example, if we need to evaluate certain input data or parse data for processing, we can write static methods to achieve these goals. Static methods work like regular functions that we define in modules but are available in the context of the class's namespace.

To illustrate how these methods can be defined and then used in Python, we created a simple program, which is shown next:

#methodsexample1.py

class Car:

c_mileage_units = "Mi"

def __init__(self, color, miles):

self.i_color = color

self.i_mileage = miles

def print_color (self):

print (f"Color of the car is {self.i_color}")

@classmethod

def print_units(cls):

print (f"mileage unit are {cls.c_mileage_unit}")

print(f"class name is {cls.__name__}")

@staticmethod

def print_hello():

print ("Hello from a static method")

if __name__ == "__main__":

car = Car ("blue", 1000)

car.print_color()

car.print_units()

car.print_hello()

Car.print_color(car);

Car.print_units();

Car.print_hello()

In this program, we did the following:

1. We created a Car class with a class attribute (c_mileage_units), a class method (print_units), a static method (print_hello), instance attributes (i_color and i_mileage), an instance method (print_color), and a constructor method (__init__).

2. We created an instance of the Car class using its constructor as car.

3. Using the instance variable (car in this example), we called the instance method, the class method, and the static method.

4. Using the class name (Car in this example), we again triggered the instance method, the class method, and the static method. Note that we can trigger the instance method using the class name, but we need to pass the instance variable as a first argument (this also explains why we need the self argument for each instance method).

The console output of this program is shown next for reference:

Color of the car is blue

mileage unit are Mi

class name is Car

Hello from a static method

Color of the car is blue

mileage unit are Mi

class name is Car

Hello from a static method

Share:

Monday, March 14, 2022

Using constructors and destructors with classes

As with any other OOP language, Python also has constructors and destructors, but the naming convention is different. The purpose of having constructors in a class is to initialize or assign values to the class- or instance-level attributes (mainly instance attributes) whenever an instance of a class is being created. In Python, the __init__method is known as the constructor and is always executed when a new instance is created. There are three types of constructors supported in Python, listed as follows:

• Default constructor: When we do not include any constructor (the __init__method) in a class or forget to declare it, then that class will use a default constructor that is empty. The constructor does nothing other than initialize the instance of a class.

• Non-parameterized constructor: This type of constructor does not take any arguments except a reference to the instance being created. The following code sample shows a non-parameterized constructor for a Name: class:

class Name:

#non-parameterized constructor

def __init__(self):

print("A new instance of Name class is \

created")

Since no arguments are passed with this constructor, we have limited functionality to add to it. For example, in our sample code, we sent a message to the console that a new instance has been created for the Name class

• Parameterized constructor: A parametrized constructor can take one or more arguments, and the state of the instance can be set as per the input arguments provided through the constructor method. The Name class will be updated with a parameterized constructor, as follows:

class Name:

#parameterized constructor

def __init__(self, first, last):

self.i_first = first

self.i_last = last

Destructors are the opposite of constructors—they are executed when an instance is deleted or destroyed. In Python, destructors are hardly used because Python has a garbage collector that handles the deletion of the instances that are no longer referenced by any other instance or program. If we need to add logic inside a destructor method, we can implement it by using a special __del__ method. It is automatically called when all references of an instance are deleted. Here is the syntax of how to define a destructor method in Python:

def __del__(self):

print("Object is deleted.")

Share:

Sunday, March 13, 2022

Classes and objects

Introducing classes and objects

A class is a blueprint for how something should be defined. It doesn't actually contain any data—it is a template that is used to create instances as per the specifications defined in a template or a blueprint.

An object of a class is an instance that is built from a class, and that is why it is also called an instance of a class. Objects in OOP are occasionally represented by physical objects such as tables, chairs, or books. On most occasions, the objects in a software program represent abstracted entities that may not be physical, such as accounts, names, addresses,and payments.

To refresh ourselves with basic concepts of classes and objects, I will define these terminologies with code examples.

Distinguishing between class attributes and instance attributes

Class attributes are defined as part of the class definition, and their values are meant to be the same across all instances created from that class. The class attributes can be accessed using the class name or instance name, although it is recommended to use a class name to access these attributes (for reading or updating). The state or data of an object is provided by instance attributes.

Defining a class in Python is simply done by using the class keyword. The following code snippet creates a Car class:

#carexample1.py

class Car:

    pass

This class has no attributes and methods. It is an empty class, and you may think this class is useless until we add more components to it. Not exactly! In Python, you can add attributes on the fly without defining them in the class. The following snippet is a valid example of code in which we add attributes to a class instance at runtime:

#carexample1.py

class Car:

    pass

if __name__ == "__main__":

    car = Car ()

    car.color = "blue"

    car.miles = 1000

    print (car.color)

    print (car.miles)

In this extended example, we created an instance (car) of our Car class and then added two attributes to this instance: color and miles. Note that the attributes added using this approach are instance attributes.

Next, we will add class attributes and instance attributes using a constructor method (__init__), which is loaded at the time of object creation. A code snippet with two instance attributes (color and miles) and the init method is shown next:

#carexample2.py

class Car:

    c_mileage_units = "Mi"

    def __init__(self, color, miles):

        self.i_color = color

        self.i_mileage = miles

if __name__ == "__main__":

    car1 = Car ("blue", 1000)

    print (car.i_color)

    print (car.i_mileage)

    print (car.c_mileage_units)

    print (Car.c_mileage_units)

In this program, we did the following:

1. We created a Car class with a c_mileage_units class attribute and two instance variables, i_color and i_mileage.

2. We created an instance (car) of the Car class.

3. We printed out the instance attributes using the car instance variable.

4. We printed out the class attribute using the car instance variable as well as the Car class name. The console output is the same for both cases.

We can update the class attributes using an instance variable or class name, but the outcome can be different. When we update a class attribute using the class name, it is updated for all the instances of that class. But if we update a class attribute using an instance variable, it will be updated only for that particular instance. This is demonstrated in the following code snippet, which is using the Car class:

#carexample3.py

#class definition of Class Car is same as in carexample2.py

if __name__ == "__main__":

car1 = Car ("blue", 1000)

car2 = Car("red", 2000)

print("using car1: " + car1.c_mileage_units)

print("using car2: " + car2.c_mileage_units)

print("using Class: " + Car.c_mileage_units)

car1.c_mileage_units = "km"

print("using car1: " + car1.c_mileage_units)

print("using car2: " + car2.c_mileage_units)

print("using Class: " + Car.c_mileage_units)

Car.c_mileage_units = "NP"

print("using car1: " + car1.c_mileage_units)

print("using car2: " + car2.c_mileage_units)

print("using Class: " + Car.c_mileage_units)

The console output of this program can be analyzed as follows:

1. The first set of print statements will output the default value of the class attribute, which is Mi.

2. After executing the car1.c_mileage_units = "km" statement, the value of the class attribute will be the same (Mi) for the car2 instance and the class-level attribute.

3. After executing the Car.c_mileage_units = "NP" statement, the value of the class attribute for car2 and the class level will change to NP, but it will stay the same (km) for car1 as it was explicitly set by us.

Share:

Saturday, March 12, 2022

Publishing a package to Test PyPI

As a next step, we will add our sample package to the PyPI repository. Before executing any command for publishing our package, we will need to create an account on Test PyPI.

Note that Test PyPI is a separate instance of the package index specifically for testing. In addition to the account with Test PyPI, we also need to add an API token to the account.

We will leave the details of creating an account and adding an API token to the account for you by following the instructions available on the Test PyPI website (https://test.pypi.org/).

To push the package to Test PyPI, we will need the Twine utility. We assume Twine is installed using the pip utility. To upload the masifutilv2 package, we will execute the following steps:

1. Create a distribution using the following command. This sdist utility will create a TAR ZIP file under a dist folder:

> python setup.py sdist

2. Upload the distribution file to Test PyPI. When prompted for a username and password, provide __token__ as the username and the API token as the password:

> twine upload --repository testpypi dist/masifutilv2-0.1.0.tar.gz

This command will push the package TAR ZIP file to the Test PyPI repository and the console output will be similar to the following:

Uploading distributions to https://test.pypi.org/legacy/

Enter your username: __token__

Enter your password:

Uploading masifutilv2-0.1.0.tar.gz

100%|█████████████████████|

5.15k/5.15k [00:02<00:00, 2.21kB/s]

We can view the uploaded file at https://test.pypi.org/project/masifutilv2/0.1.0/ after a successful upload.

Installing the package from PyPI

Installing the package from Test PyPI is the same as installing from a regular repository, except that we need to provide the repository URL by using the index-url arguments.

The command and the console output will be similar to the following:

> pip install --index-url https://test.pypi.org/simple/ --nodeps masifutilv2

This command will present console output similar to the following:

Looking in indexes: https://test.pypi.org/simple/

Collecting masifutilv2

Downloading https://test-files.pythonhosted.org/

packages/b7/e9/7afe390b4ec1e5842e8e62a6084505cbc6b9

f6adf0e37ac695cd23156844/masifutilv2-0.1.0.tar.gz (2.3 kB)

Building wheels for collected packages: masifutilv2

Building wheel for masifutilv2 (setup.py) ... done

Created wheel for masifutilv2: filename=masifutilv2-

0.1.0-py3-none-any.whl size=3497

sha256=a3db8f04b118e16ae291bad9642483874

f5c9f447dbee57c0961b5f8fbf99501

Stored in folder: /Users/muasif/Library/Caches/pip/

wheels/1c/47/29/95b9edfe28f02a605757c1

f1735660a6f79807ece430f5b836

Successfully built masifutilv2

Installing collected packages: masifutilv2

Successfully installed masifutilv2-0.1.0

As we can see in the console output, pip is searching for the module in Test PyPI. Once it finds the package with the name masifutilv2, it starts downloading and then installing it in the virtual environment.

In short, we have observed that once we create a package using the recommended format and style, then publishing and accessing the package is just a matter of using Python utilities and following the standard steps.

Share:

Friday, March 11, 2022

Installing from the local source code using pip

Once we have updated the package with new files, we are ready to install it using the pip utility. The simplest way to install it is by executing the following command with the path to the masifutilv2 folder:

> pip install <path to masifutilv2>

The following is the console output of the command when run without installing the wheel package:

Processing ./masifutilv2

Using legacy 'setup.py install' for masifutilv2, since package 'wheel' is not installed.

Installing collected packages: masifutilv2

Running setup.py install for masifutilv2 ... done

Successfully installed masifutilv2-0.1.0

The pip utility installed the package successfully but using the egg format since the wheel package was not installed. Here is a view of our virtual environment after the installation:


After installing the package under the virtual environment, we tested it with our pkgmain3.py program, which worked as expected.

As a next step, we will install the wheel package and then reinstall the same package again. Here is the installation command:

> pip install <path to masifutilv2>

The console output will be similar to the following:

Processing ./masifutilv2

Building wheels for collected packages: masifutilv2

Building wheel for masifutilv2 (setup.py) ... done

Created wheel for masifutilv2: filename=masi

futilv2-0.1.0-py3-none-any.whl size=3497

sha256=038712975b7d7eb1f3fefa799da9e294b34

e79caea24abb444dd81f4cc44b36e

Stored in folder: /private/var/folders/xp/g88fvmgs0k90w0rc_

qq4xkzxpsx11v/T/pip-ephem-wheel-cache-l2eyp_wq/wheels/

de/14/12/71b4d696301fd1052adf287191fdd054cc17ef6c9b59066277

Successfully built masifutilv2

Installing collected packages: masifutilv2

Successfully installed masifutilv2-0.1.0

The package is installed successfully using wheel this time and we can see it appears in our virtual environment as follows:


In this blog, we have installed a package using the pip utility from the local source code. In the next blog, we will publish the package to a centralized repository (Test PyPI).

Share:

Thursday, March 10, 2022

Sharing a package

To distribute Python packages and projects across communities, there are many tools available. We will focus only on the tools that are recommended as per the guidelines provided by PyPA.

In this blog, we will be covering installing and distributing packaging techniques. A few tools that we will use or are at least worth mentioning in this section as a reference are as follows:

• distutils: This comes with Python with base functionality. It is not easy to extend for complex and custom package distribution.

• setuputils: This is a third-party tool and an extension of distutils and is recommended for building packages.

• wheel: This is for the Python packaging format and it makes installations faster and easier as compared to its predecessors.

• pip: pip is a package manager for Python packages and modules, and it comes as part of Python if you are installing Python version 3.4 or later. It is easy to use pip to install a new module by using a command such as pip install

<module name>.

• The Python Package Index (PyPI): This is a repository of software for the Python programming language. PyPI is used to find and install software developed and shared by the Python community.

• Twine: This is a utility for publishing Python packages to PyPI.

In the next subsections, we will update the masifutil package to include additional components as per the guidelines provided by PyPA. This will be followed by installing the updated masifutil package system-wide using pip. In the end, we will publish the updated masifutil package to Test PyPI and install it from Test PyPI.

Building a package as per the PyPA guidelines

PyPA recommends using a sample project for building reusable packages and it is available at https://github.com/pypa/sampleproject. A snippet of the sample project from the GitHub location is as shown:


Let me introduce key files and folders, which are important to understand before we use them for updating our masifutil package:

• setup.py: This is the most important file, which has to exist at the root of the project or package. It is a script for building and installing the package. This file contains a global setup() function. The setup file also provides a command-line interface for running various commands.

• setup.cfg: This is an ini file that can be used by setup.py to define defaults.

• setup() args: The key arguments that can be passed to the setup function are as follows:

a) Name

b) Version

c) Description

d) URL

e) Author

f) License

• README.rst/README.md: This file (either reStructured or Markdown format) can contain information about the package or project.

• license.txt: The license.txt file should be included with every package with details of the terms and conditions of distribution. The license file is important, especially in countries where it is illegal to distribute packages without the appropriate license.

• MANIFEST.in: This file can be used to specify a list of additional files to include in the package. This list of files doesn't include the source code files (which are automatically included).

• <package>: This is the top-level package containing all the modules and packages inside it. It is not mandatory to use, but it is a recommended approach.

• data: This is a place to add data files if needed.

• tests: This is a placeholder to add unit tests for the modules.

As a next step, we will update our previous masifutil package as per the PyPA guidelines. Here is the new folder and file structure of the updated masifutilv2 package:


We have added data and tests directories, but they are actually empty for now. We will evaluate the unit tests in a later chapter to complete this topic.

The contents of most of the additional files are covered in the sample project and thus will not be discussed here, except the setup.py file.

We updated setup.py with basic arguments as per our package project. The details of the rest of the arguments are available in the sample setup.py file provided with the sample project by PyPA. Here is a snippet of our setup.py file:

from setuptools import setup

setup(

name='masifutilv2',

version='0.1.0',

author='Muhammad Asif',

author_email='ma@example.com',

packages=['masifutil', 'masifutil/advcalc'],

python_requires='>=3.5, <4',

url='http://pypi.python.org/pypi/PackageName/',

license='LICENSE.txt',

description='A sample package for illustration purposes',

long_description=open('README.md').read(),

install_requires=[

],

)

With this setup.py file, we are ready to share our masifutilv2 package locally as well as remotely, which we will discuss in the next blog.

Share:

Wednesday, March 9, 2022

Using the PYTHONPATH environment variable

This is a convenient way to add our package folder to sys.path, which the Python interpreter will use to search for the package and modules if not present in the built-in library. Depending on the operating system we are using, we can define this variable as follows.

In Windows, the environment variable can be defined using either of the following options:

• The command line: Set PYTHONPATH = "C:\pythonpath1;C:\pythonpath2". This is good for one active session.

• The graphical user interface: Go to My Computer | Properties | Advanced System Settings | Environment Variables. This is a permanent setting.

In Linux and macOS, it can be set using export PYTHONPATH= `/some/path/`.

If set using Bash or an equivalent terminal, the environment variable will be effective for the terminal session only. To set it permanently, it is recommended to add the environment variable at the end of a profile file, such as ~/bash_profile.

If we execute the pkgmain3.py program without setting PYTHONPATH, it returns an error: ModuleNotFoundError: No module named 'masifutil'. This is again expected as the path of the masifutil package is not added to PYTHONPATH.

In the next step, we will add the folder path containing masifutil to the PYTHONPATH variable and rerun the pkgmain3 program. This time, it works without any error and with the expected console output.

Using the .pth file under the Python site package

This is another convenient way of adding packages to sys.path. This is achieved by defining a .pth file under the Python site packages. The file can hold all the folders we want to add to sys.path.

For illustration purposes, we created a my.pth file under venv/lib/Python3.7/ site-packages. As we can see in Figure shown below, we added a folder that contains our masifutil package. With this simple .pth file, our main script pkymain3.py program works fine without any error and with expected console output:


The approaches we discussed to access custom packages are effective to reuse the packages and modules on the same system with any program. In the next blog, we will explore how to share packages with other developers and communities.

Share:

Tuesday, March 8, 2022

Accessing packages from any location

The package we built in the previous subsection is accessible only if the program calling the modules is at the same level as the package location. This requirement is not practical for code reusability and code sharing.

Let us discuss a few techniques to make packages available and usable from any program on any location in our system.

Appending sys.path

This is a useful option for setting sys.path dynamically. Note that sys.path is a list of directories on which a Python interpreter searches every time it executes an import statement in a source program. By using this approach, we are appending (adding) paths of directories or folders containing our packages to sys.path.

For the masifutil package, we will build a new program, pkgmain3.py, which is a copy of pkgmain2.py (to be updated later) but is kept outside the folder where our masifutil package is residing. pkgmain3.py can be in any folder other than the mypackages folder. 

When we execute the pkgmain3.py program, it returns an error:

ModuleNotFoundError: No module named 'masifutil'. This is expected as the path of the masifutil package is not added to sys.path. To add the package folder to sys.path, we will update the main program; let's name it pkgmain4.py, with additional statements for appending sys.path, which is shown next:

# pkgmain4.py with sys.path append code

import sys

sys.path.append('/Users/muasif/Google Drive/PythonForGeeks/

source_code/chapter2/mypackages')

import masifutil

def my_main():

""" This is a main function which generates two random\

numbers and then apply calculator functions on them """

x = masifutil.random_2d()

y = masifutil.random_1d()

sum = masifutil.add(x,y)

diff = masifutil.subtract(x,y)

sroot = masifutil.sqrt(x)

log10x = masifutil.log(x)

log2x = masifutil.ln(x)

print("x = {}, y = {}".format(x, y))

print("sum is {}".format(sum))

print("diff is {}".format(diff))

print("square root is {}".format(sroot))

print("log base of 10 is {}".format(log10x))

print("log base of 2 is {}".format(log2x))

""" This is executed only if the special variable '__name__' is

set as main"""

if __name__ == "__main__":

my_main()

After adding the additional lines of appending sys.path, we executed the main script without any error and with the expected console output. This is because our masifutil package is now available on a path where the Python interpreter can load it when we are importing it in our main script.

Alternative to appending sys.path, we can also use the site.addsitedir function from the site module. The only advantage of using this approach is that this function also looks for .pth files within the included folders, which is helpful for adding additional folders such as sub-packages. A snippet of a sample main script (pktpamin5.py) with the addsitedir function is shown next:

# pkgmain5.py

import site

site.addsitedir('/Users/muasif/Google Drive/PythonForGeeks/

source_code/chapter2/mypackages')

import masifutil

#rest of the code is the same as in pkymain4.py

Note that the directories we append or add using this approach are available only during the program execution. To set sys.path permanently (at the session or system level), the approaches that we will discuss in the next blogs are more helpful.

Share:

Monday, March 7, 2022

Adding some import statements

Next, we will explore how to add some import statements to the init file. Let's start with importing the modules inside the init file. In this top-level init file, we will import all functions as shown next:

#__init__ file for package 'masifutil'

from .mycalculator import add, subtract

from .myrandom import random_1d, random_2d

from .advcalc.advcalculator import sqrt, log, ln

Note the use of . before the module name. This is required for Python for the strict use of relative imports. As a result of these three lines inside the init file, the new main script will become simple and the sample code is shown next:

# pkgmain2.py with main function

import masifutil

def my_main():

""" This is a main function which generates two random\

numbers and then apply calculator functions on them """

x = masifutil.random_2d()

y = masifutil.random_1d()

sum = masifutil.add(x,y)

diff = masifutil.subtract(x,y)

sroot = masifutil.sqrt(x)

log10x = masifutil.log(x)

log2x = masifutil.ln(x)

print("x = {}, y = {}".format(x, y))

print("sum is {}".format(sum))

print("diff is {}".format(diff))

print("square root is {}".format(sroot))

print("log base of 10 is {}".format(log10x))

print("log base of 2 is {}".format(log2x))

""" This is executed only if the special variable '__name__' is

set as main"""

if __name__ == "__main__":

my_main()

The functions of the two main modules and the sub-package module are available at the main package level and the developers do not need to know the underlying hierarchy and structure of the modules within the package. This is the convenience we discussed earlier of using import statements inside the init file.

We build the package by keeping the package source code in the same folder where the main program or script resides. This works only to share the modules within a project. Next, we will discuss how to access the package from other projects and from any program from anywhere.

Share: