Difference Between .py and .pyc Files: A Python Beginners Guide

Are you wondering what the difference is between Python .py files and .pyc files? You are in the right place.

Files with extension .py contain Python code that is human readable. On the other side .pyc files contain bytecode that is not human readable. Files with .py extension are compiled into .pyc files that are then processed by the Python interpreter.

Don’t worry if this doesn’t fully make sense, we will go through a few examples that will make things clear.

And I will also show you when the compilation of .py files into .pyc files happens.

Let’s get into it!

What Are .py and .pyc Files in Python?

Files with .py extension are Python source files, the files in which you write your Python code.

The Python code you write in .py files is not executed in the same format by the machine on which you run your code.

Before being executed, the code in the .py files is compiled into .pyc files.

Imagine the compilation process as a translation from one language to another language.

Files with .pyc extension are the result of compiling files with .py extension. A .pyc file for a given Python module gets automatically created when that module is imported.

Note: as a Python developer you will only make code changes to .py files.

To see the difference between the two types of files let’s create first a Python module in a file called app.py.

For all the examples in this tutorial I’m creating app.py inside the /var/tmp/ directory.

The app.py file contains code for the app module and in this example it contains a single function:

def get_full_name(first_name, last_name):
    return "{} {}".format(first_name, last_name)

To show you the format of a .pyc file we will first use Python 2.

You will understand why in the next section…

Open the Python shell and import the app module:

$ python2

Python 2.7.16 (default, Dec 21 2020, 23:00:36) 
[GCC Apple LLVM 12.0.0 (clang-1200.0.30.4) [+internal-os, ptrauth-isa=sign+stri on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
>>> 

Now exit from the Python shell.

Notice that the file app.pyc has been created:

$ ls -al app*
-rw-r--r--  1 codefather  wheel   91 Mar 20 00:11 app.py
-rw-r--r--  1 codefather  wheel  261 Mar 20 00:12 app.pyc

Let’s have a look at the content of the .pyc file…

$ cat app.pyc
?
d?ZdS(cCsdj||?S(Ns{} {}(tformat(t
get_full_namesN(R(((sapp.py<module>t%

The .pyc file is not fully readable because it’s a compiled version of the original .py file. The app.pyc file contains bytecode.

What is bytecode?!?

Think about bytecode as a low level representation of the Python code in your .py file. Low level means that it is nearer to the language that a computer can understand compared to the original Python code.

How is a Compiled File Created in Python?

We have seen that a compiled (.pyc) file is created when a Python module gets imported.

But, what creates compiled Python files?

The answer is: it depends on the Python implementation you are using.

The reference implementation of Python is called CPython and it’s written in C and Python. In this implementation Python code is compiled into bytecode by a compiler before being interpreted.

How can you confirm if you are using CPython?

To verify the Python implementation you are using on your machine you can use the Python platform module. And specifically the python_implementation() function.

Let’s see what Python 2 implementation I use on this machine.

Python 2.7.16 (default, Dec 21 2020, 23:00:36) 
[GCC Apple LLVM 12.0.0 (clang-1200.0.30.4) [+internal-os, ptrauth-isa=sign+stri on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import platform
>>> platform.python_implementation()
'CPython'

The Python implementation on this machine is CPython that as I explained before is the reference implementation of Python.

And let’s see what the output is for Python 3.

Python 3.8.2 (default, Dec 21 2020, 15:06:03) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import platform
>>> platform.python_implementation()
'CPython'

Same implementation: CPython.

Where are Compiled Files Created when Using Python 3?

In the previous section we have used Python 2. We have seen that a .pyc file has been created in the same directory of the .py file when importing the module.

Note: Considering that Python 2 is very old you should really be using Python 3. In this tutorial I’m also using Python 2 to show you the difference in behaviour between the two versions of Python.

Let’s try to do the same test with Python 3.

Delete the .pyc file created before and open the Python shell using Python 3.

Then import the app module…

$ rm app.pyc
$ python3
Python 3.8.2 (default, Dec 21 2020, 15:06:03) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
>>>

Now exit from the Python shell and verify if a new .pyc file exists.

>>> exit()
$ ls -al app*
-rw-r--r--  1 codefather  wheel  91 Mar 20 00:11 app.py

That’s weird…

For some reason the compiled file with extension .pyc doesn’t exist.

Why?!?

That’s because…

In Python 3 the compiled version of the code for a given module is created in a different location compared to what happens with Python 2.

Let’s reopen the Python 3 shell…

…I want to show you something.

Python 3.8.2 (default, Dec 21 2020, 15:06:03) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
>>> dir(app)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'get_full_name']

After importing the app module we can use the dir() built-in function to get a list of module attributes we can access.

There’s one attribute I want you to focus on: __cached__.

Let’s check its value…

>>> app.__cached__
'/Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc'

Note: as mentioned before for all the examples in this tutorial I have created app.py inside the /var/tmp/ directory.

The __cached__ attribute for a Python module is the path of the compiled version of that module. The addition of this attribute was a proposal part of PEP 3147.

Note: PEP stands for Python Enhancement Proposals.

You can see that the format of the name of the .pyc file has changed compared to Python 2. When using Python 3 the filename also contains the Python implementation (cpython) and the version of Python (38).

The file path depends on your operating system.

Let’s verify that the file app.cpython-38.pyc is actually in that directory.

$ ls -al /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc
-rw-r--r--  1 codefather  staff  257 Mar 20 00:19 /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc

We have confirmed that the compiled file is in that directory.

It would have been harder to find this path without retrieving the value of the __cached__ attribute!

When Do .pyc Files Get Updated?

Let’s continue working on the example in the previous section.

We want to understand when .pyc files get updated.

Reopen the Python 3 shell, import the app module and check if anything has changed with the .pyc file:

$ python3
Python 3.8.2 (default, Dec 21 2020, 15:06:03) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
>>> exit()

$ ls -al /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc
-rw-r--r--  1 codefather  staff  257 Mar 20 00:19 /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc

As you can see from the output of the ls command filesize and last modify date for app.cpython-38.pyc have not changed.

Now modify the Python function defined in app.py and modify the name of the function from get_full_name() to get_user_full_name:

def get_user_full_name(first_name, last_name):
    return "{} {}".format(first_name, last_name)

Open the Python 3 shell, import the app module and exit from the shell.

>>> import app
>>> exit()

Verify if anything has changed with the compiled file app.cpython-38.pyc:

$ ls -al /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc
-rw-r--r--  1 codefather  staff  262 Mar 20 00:31 /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc

Both filesize and last modify date have changed.

That’s because the Python interpreter has detected a change in the module app.py and has recompiled the code into a new .pyc file.

Python recreates the .pyc file for a given module when that module is modified and reimported.

Can You Delete .pyc Files?

You can delete .pyc files, if you do that and then you import that module again the .pyc file related to that module gets recreated.

Here you can see the .pyc created before.

$ ls -al /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc
-rw-r--r--  1 codefather  staff  262 Mar 20 00:31 /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc

Let’s delete it and import the app module again…

$ rm /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc
$ python3

Python 3.8.2 (default, Dec 21 2020, 15:06:03) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
>>> exit()

$ ls -al /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc
-rw-r--r--  1 codefather  staff  262 Mar 20 00:35 /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc

Python has recreated the .pyc file. It has recompiled the .py file into this .pyc file.

Can You Delete .py Files with Python 2?

That’s an interesting one…

Let’s try to delete the app.py file, the file that contains our Python code.

What do you think happens if we then try to import the app module?

Let’s start with Python 2 and before deleting the .py file make sure the .pyc file exists in the same directory of the .py file.

If the .pyc file doesn’t exist open the Python 2 shell and import the app module in order to recreate the .pyc file.

$ python2

Python 2.7.16 (default, Dec 21 2020, 23:00:36) 
[GCC Apple LLVM 12.0.0 (clang-1200.0.30.4) [+internal-os, ptrauth-isa=sign+stri on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
>>> exit()

Now delete app.py and reopen the Python 2 shell to import the app module.

$ rm app.py
$ python2

Python 2.7.16 (default, Dec 21 2020, 23:00:36) 
[GCC Apple LLVM 12.0.0 (clang-1200.0.30.4) [+internal-os, ptrauth-isa=sign+stri on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
>>> dir(app)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'get_user_full_name']
>>> app.get_user_full_name('John', 'Smith')
'John Smith'

Interestingly, after deleting a .py file the Python 2 interpreter doesn’t throw any errors when importing that module if a .pyc file for that module exists.

Can You Delete .py Files with Python 3?

Let’s continue where we left in the previous section where we have seen how Python 2 behaves when deleting a .py file.

And now let’s use Python 3.

The app.py file doesn’t exist already in the current directory, /var/tmp, so we can just open the Python 3 shell and try to import the app module.

$ python3
Python 3.8.2 (default, Dec 21 2020, 15:06:03) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: bad magic number in 'app': b'\x03\xf3\r\n'

We get back a weird error bad magic number in ‘app’.

What does it mean?

One scenario in which the bad magic number error occurs is when Python 3 tries to load a .pyc file compiled with Python 2.

Apparently this is happening here because Python 3 finds a .pyc file in the current directory and it tries to load it.

Let’s remove the .pyc file from the current directory and then try to import the app module again.

$ pwd
/var/tmp
$ rm app.pyc
$ python3
Python 3.8.2 (default, Dec 21 2020, 15:06:03) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import app
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'app'

We receive a ModuleNotFoundError back despite the fact that the .pyc file still exists in the directory in which it was created when importing the app module with Python 3 (see ls output below).

$ ls -al /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc
-rw-r--r--  1 codefather  staff  262 Mar 20 00:35 /Users/codefather/Library/Caches/com.apple.python/private/var/tmp/app.cpython-38.pyc

Conclusion

https://youtu.be/xNQoMKiCok8Well done!

Now you know what the difference is between .py and .pyc files in Python.

You also know what role .pyc files play in the execution of your Python programs: they are generated by compiling .py files and then they are interpreted.

We have also seen how the compilation of .pyc file changes between Python 2 and Python 3.

And if you are new to Python I recommend going through this Python coding beginner tutorial.

Leave a Comment