Porting from Python2 to Python3 ?

Porting from Python2 to Python3 ?

Python 2 reached its end of life in 2020, there are tools and practices to help migrate code to Python 3.

Let's learn how to port python2 script with python3 and what are the common challanges you would be facing while porting python2 to python3.

There is built-in tools 2to3 that may comes handy but doesn't gurantee to fully port the script. You may see runtime issues if you only rely on this tool. Here, I gonna highlight how can you take python2 to make sure it works in Python3.

  • Use the 2to3 Tool

Python provides a built-in tool called 2to3 that automates many common changes needed to migrate Python 2 code to Python 3.

2to3 <script.py>
This will show the differences and suggested changes.

2to3 -w <script.py>
The -w flag modifies the file in-place and creates a backup with a .bak extension.

  • Modern Linux OS are by default coming with Python3. Now, if you have a script with shebang /usr/bin/python that will not work in now. You have to explicitely use /usr/bin/python3 or you have to link /usr/bin/python to /usr/bin/python3
ln -sf /usr/bin/python3 /usr/bin/python
  • Print statement in Python works with bracket.
    so statment like print "Hello World" will not work in Python3. You have to adapt it like print("Hello World")
  • Python3 introduced a new method for formatting strings, allowing you to directly insert variables within {} at any position where you want to display the data.
def calculate_bmr(weight, height, age, gender):
    if gender.lower() == 'male':
        # BMR calculation for men
        bmr = 88.362 + (13.397 * weight) + (4.799 * height) - (5.677 * age)
    elif gender.lower() == 'female':
        # BMR calculation for women
        bmr = 447.593 + (9.247 * weight) + (3.098 * height) - (4.330 * age)
    else:
        return None  # Invalid gender
    return bmr

def main():
    print("Enter your details to calculate your BMR:")
    name = input("Name: ")
    age = int(input("Age: "))
    weight = float(input("Weight (in kg): "))
    height = float(input("Height (in cm): "))
    gender = input("Gender (male/female): ")

    bmr = calculate_bmr(weight, height, age, gender)

    if bmr:
        print(f"\nBMR Profile for {name}:")
        print(f"Age: {age}")
        print(f"Weight: {weight} kg")
        print(f"Height: {height} cm")
        print(f"Gender: {gender}")
        print(f"Your BMR is: {bmr:.2f} calories/day")
    else:
        print("Invalid gender input. Please enter 'male' or 'female'.")

if __name__ == "__main__":
    main()

Program to print your BMR.

  • Unicode and String Handling
    • In Python 2, strings are ASCII by default, while Python 3 treats strings as Unicode by default.
    • Update Python 2 code that uses unicode() and string literals to use Python 3’s str():
# Python 2:
s = unicode("Hello", "utf-8")

# Python 3:
s = "Hello"

unicode in python2 and python3

  • Bytes format ouput from subprocess.

    • By default, the output from subprocess.run() or subprocess.Popen() will indeed be in bytes format unless you explicitly specify text=True (or its older equivalent universal_newlines=True).

    • When using text=True, the output will be automatically decoded to a string using the system's default encoding (utf-8).

#!/usr/bin/python3
import subprocess

def command_executor(cmd):
    try:
        process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        #process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) #On Python3.8 for string output.
        stdout, stderr = process.communicate()

        if process.returncode != 0:
            print(f"Error: {stderr}")
        else:
            return stdout
    except Exception as e:
            print(f"An exception occurred: {str(e)}")

command = 'rpm -qa | grep kernel';

result = command_executor(command)
print(f"result: {result}")

---------------------------------------------------------------------------------
## Script o/p:
result: b'kernel-srpm-macros-1.0-13.el9.noarch\nkernel-headers-5.14.0-427.35.1.el9_4.x86_64\n'

Python3 default Bytes

  • Common String formatting that you don't have to change if you are moving from Python2 to Python2
    username = get_user()
    print("User logged in here: %s" % (username))
  • Integer Division
    • In Python 2, dividing integers like 5/2 yields 2. In Python 3, it yields 2.5.
    • To ensure integer division, use // in Python 3
# Python 2 style: 
result = 5 / 2  # Yields 2 in Python 2, 2.5 in Python 3

# Python 3 style for integer division:
result = 5 // 2  # Yields 2 in both Python 2 and 3
  • Iterators
    • In Python 2, functions like range() and map() return lists. In Python 3, they return iterators.
    • Use list() to explicitly convert iterators to lists when necessary
# Python 2:
range_list = range(5)

# Python 3:
range_list = list(range(5))
  • Module Dependencies

You may see some of the dependent module are not available in Python3. So your sources are using particular library which is not available in Python3. It is recommneded to adapt new library which is available in Python3 or port the dependent modules also in order to make sure it work in new environment.

  • Unit Tests
    Before moving the ported script to production, I recommend writing unit tests for all scripts. This will help ensure that if you change the OS environment, you'll be able to handle any potential issues that arise.

Read more