Pythonで自作のモジュールやパッケージをimportする方法

※本サイトにはプロモーション・広告が含まれています。

(最終更新月:2023年1月)

✔このような方へ向けて書かれた記事となります

「Pythonで別ファイルに保存した関数を使いたい」

「ぐちゃぐちゃになってきたクラスや関数を整理したい」

「Djangoアプリのフォルダ・ファイル構成を整理したい」

✔当記事を通じてお伝えすること

  • モジュールとパッケージの定義やその違い
  • モジュール化の実例
  • パッケージ化の実例

当記事をお読みいただくと、モジュール化やパッケージ化がどのようなものかをご理解いただけるのはもちろん、実例もご覧いただけるのですぐにコーディングに活かせます。

ぜひ最後までご覧ください。

✔YouTube解説動画

当記事の内容は動画を見ながら進めると、約15分程度で完了します。

動画ならではの情報も解説しているので、記事と一緒にご覧ください。

動画の概要欄には、単元ごとのコードを紹介しているgithubページも載せています。

筆者プロフィール

筆者プロフィールアイコン

【現職】プロダクトマネージャー

【副業】ブログ(月間17万PV)/YouTube/Web・アプリ制作

「プログラミング × ライティング × 営業」の経験を活かし、30後半からのIT系職へシフト。当サイトでは、実際に手を動かせるWebアプリの開発を通じて、プログラミングはもちろん、IT職に必要な情報を提供していきます。

【当ブログで紹介しているサイト】

当サイトチュートリアルで作成したデモ版日報アプリ

Django × Reactで開発したツール系Webアプリ

✔人に見せても恥ずかしくないコードを書こう

「リーダブルコード」は、わかりやすく良いコードの定義を教えてくれる本です。

  • 見るからにきれいなコードの書き方
  • コードの分割方法
  • 変数や関数の命名規則

エンジニアのスタンダートとすべき基準を一から解説しています。

何回も読むのに値する本なので、ぜひ手にとって読んでみてください。

モジュールとパッケージの定義やそれぞれの違い

まずはモジュールやパッケージの定義をご覧ください。

定義がわからなければ、このあとのインポート方法でつまづいてしまうでしょう。

  • モジュールとは
  • パッケージとは
  • ライブラリとは

モジュールとは?

モジュールとは、Pythonの関数やクラスが記述されたPythonファイルのことをいいます。

モジュールをインポートすることで、その中の関数やクラスが使えるようになるのです。

Djangoフレームワークを例にすれば、views.pyやmodels.pyなどのファイルそれぞれがモジュールといえます。

パッケージとは?

パッケージとは、モジュールを束ねたフォルダのことです。

パッケージをインポートすることで、その中のモジュールにアクセスできるようになります。

ただし単にファイルが入っているフォルダでは、パッケージ化はできません。

複数のモジュール(ファイル)と合わせて、__init__.pyが必要になります。

ライブラリとは?

ライブラリとは、パッケージ化されたフォルダをまとめたもののことをいいます。

より複雑な機能が備わっているのがライブラリといえるでしょう。

モジュール化の実例

現在開発中の日報アプリでは、いくつかのアクセス制限クラスが別々の場所に作られています。

なぜなら現段階では、使用する場所が多くないからです。

ただしページが増えてくるにつれ、使用する場所も増えるかもしれません。

点々としているアクセス制限クラスを一箇所にまとめて、流用しやすく、また変更を加えやすくしていきましょう。

utils(フォルダ) > access_restrictions.py(ファイル)

from django.contrib.auth.mixins import UserPassesTestMixin

class OwnerOnly(UserPassesTestMixin):
    def test_func(self):
        nippo_instance = self.get_object()
        return nippo_instance.user == self.request.user

class OwnProfileOnly(UserPassesTestMixin):
    def test_func(self):
        profile_obj = self.get_object()
        return profile_obj == self.request.user.profile

nippo > views.py

from utils.access_restrictions import OwnerOnly

accounts > views.py

from utils.access_restrictions import OwnProfileOnly

パッケージ化の実例

現在開発中の日報アプリでは、以下のファイルに性質が異なるクラスが定義されています。

その理由は、同じaccountsというアプリに2種類のモデルクラスを定義しているからです。

accounts > forms.py

from django import forms
from django.forms.fields import DateField
from django.utils.translation import gettext, gettext_lazy as _
from django.contrib.auth.forms import UserChangeForm

from .models import User, Profile, GENDER_CHOICE

#GENDER_CHOICE = [(None, "--"), ("m", "男性"), ("f", "女性")]はmodels.pyで定義してます

class CustomAdminChangeForm(UserChangeForm):
#Profileクラスのフィールドを追記します
    username = forms.CharField(max_length=100)
    department = forms.CharField(max_length=100, required=False)
    phone_number = forms.IntegerField(required=False) 
    gender = forms.ChoiceField(choices=GENDER_CHOICE, required=False)
    birthday = DateField(required=False)

    class Meta:
        model = User
        fields =('email', 'password', 'active', 'admin')

#Profileが存在する場合は、初期値にデータを格納する
    def __init__(self, *args, **kwargs):
        user_obj = kwargs["instance"]
        if hasattr(user_obj, "profile"):
            profile_obj = user_obj.profile
            self.base_fields["username"].initial = profile_obj.username
            self.base_fields["department"].initial = profile_obj.department
            self.base_fields["phone_number"].initial = profile_obj.phone_number
            self.base_fields["gender"].initial = profile_obj.gender
            self.base_fields["birthday"].initial = profile_obj.birthday
        super().__init__(*args, **kwargs)

#保存機能の定義
    def save(self, commit=True):
        user_obj = super().save(commit=False)
        username = self.cleaned_data.get("username")
        department = self.cleaned_data.get("department")
        phone_number = self.cleaned_data.get("phone_number")
        gender = self.cleaned_data.get("gender")
        birthday = self.cleaned_data.get("birthday")
        if hasattr(user_obj, "profile"):
            profile_obj = user_obj.profile
        else:
            profile_obj = Profile(user=user_obj)
        if username is not None:
            profile_obj.username = username
        if department is not None:
            profile_obj.department = department
        if phone_number is not None:
            profile_obj.phone_number = phone_number
        if gender is not None:
            profile_obj.gender=gender
        if birthday is not None:
            profile_obj.birthday = birthday
        profile_obj.save()
        if commit:
            user_obj.save()
        return user_obj

class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = Profile
        exclude = ["user"]
        
    def clean_username(self):
        username = self.cleaned_data.get("username")
        user_email = self.instance.user.email
        if username == user_email:
            raise forms.ValidationError("ユーザー名を変更してください")
        elif "@" in username:
            raise forms.ValidationError("ユーザー名にEメールアドレスは使用できません")
        return username

これからクラスなどが増える可能性を考えると、パッケージ化してどこに何が書いてあるのかが分かりやすいほうがよいかもしれません。

以下のとおり、変更を加えましょう。

accounts > forms > account_forms.py

from django import forms
from django.forms.fields import DateField
from django.contrib.auth.forms import UserChangeForm

from accounts.models import User, Profile, GENDER_CHOICE

class CustomAdminChangeForm(UserChangeForm):
#Profileクラスのフィールドを追記します
    username = forms.CharField(max_length=100)
    department = forms.CharField(max_length=100, required=False)
    phone_number = forms.IntegerField(required=False) 
    gender = forms.ChoiceField(choices=GENDER_CHOICE, required=False)
    birthday = DateField(required=False)

    class Meta:
        model = User
        fields =('email', 'password', 'active', 'admin')

#Profileが存在する場合は、初期値にデータを格納する
    def __init__(self, *args, **kwargs):
        user_obj = kwargs["instance"]
        if hasattr(user_obj, "profile"):
            profile_obj = user_obj.profile
            self.base_fields["username"].initial = profile_obj.username
            self.base_fields["department"].initial = profile_obj.department
            self.base_fields["phone_number"].initial = profile_obj.phone_number
            self.base_fields["gender"].initial = profile_obj.gender
            self.base_fields["birthday"].initial = profile_obj.birthday
        super().__init__(*args, **kwargs)

#保存機能の定義
    def save(self, commit=True):
        user_obj = super().save(commit=False)
        username = self.cleaned_data.get("username")
        department = self.cleaned_data.get("department")
        phone_number = self.cleaned_data.get("phone_number")
        gender = self.cleaned_data.get("gender")
        birthday = self.cleaned_data.get("birthday")
        if hasattr(user_obj, "profile"):
            profile_obj = user_obj.profile
        else:
            profile_obj = Profile(user=user_obj)
        if username is not None:
            profile_obj.username = username
        if department is not None:
            profile_obj.department = department
        if phone_number is not None:
            profile_obj.phone_number = phone_number
        if gender is not None:
            profile_obj.gender=gender
        if birthday is not None:
            profile_obj.birthday = birthday
        profile_obj.save()
        if commit:
            user_obj.save()
        return user_obj

accounts > forms > profile_forms.py

from django import forms
from accounts.models importProfile

class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = Profile
        exclude = ["user"]
        
    def clean_username(self):
        username = self.cleaned_data.get("username")
        user_email = self.instance.user.email
        if username == user_email:
            raise forms.ValidationError("ユーザー名を変更してください")
        elif "@" in username:
            raise forms.ValidationError("ユーザー名にEメールアドレスは使用できません")
        return username

accounts > forms > __init__.py

from .account_forms import *
from .profile_forms import *

フォルダ内は以下のとおりとなります。

forms
├── __init__.py
├── account_forms.py
└── profile_forms.py

このとおりとすることで、formsからどのモジュールもインポートできるようになるのです。

from .forms import *

まとめ:Pyhonで自作ファイルをimportすると構成がすっきりする

当記事の内容をまとめます。

  • モジュールとは、Pythonの関数やクラスが記述されたPythonファイルのこと
  • パッケージとは、モジュールを束ねたフォルダのこと
  • モジュールやパッケージを活用すると、どこに何が書いてあるかがわかりやすくなる

Pythonで書かれたDjangoフレームワークには、便利なモジュールやパッケージが備わっています。

ただし、開発を進めているとそれだけでは済まないことが多いです。

必要な関数やクラスをどこに書いたかが明確になるようにパッケージ化・モジュール化を活用してください。

次回のDjangoチュートリアル記事では、日報アプリに検索機能を実装する方法をお伝えしていきます。

一度理解すれば応用もきく方法なので、ぜひ最後まで見てください。

タイトルとURLをコピーしました