データ検証とクラス

Skip to content

これは機械翻訳されたテキストであり、誤りを含む可能性があります!

Python (およびほとんどの他の言語でも) では、独自のオブジェクトを作成し、独自の値を、ルール、および関数を定義できます。これはクラス (英語では classes) と呼ばれます。クラスを使用して、関連するデータと関数を 1 つの単位にまとめます。たとえば、ordre_idkunde_navnprodukter などのデータと、legg_til_produkt()beregn_total() などの関数を持つ Ordre クラスなどです。

Dictionary (JSON) vs Klasser (Objekter)

JSON (JavaScript Object Notation) は、プログラミング言語に依存せずにデータを保存および転送するための形式であり、クラスは特定のプログラミング言語における構造です。

ネットワーク経由でデータを送信したり、ファイルに保存したりする場合は、JSON(またはデータベースのテーブル)をよく使用します。

コード内で構造化されたデータを使用する場合は、クラスを使用します。

このモジュールでは、クラスを使ってデータを検証する方法を見ていきます。

最も簡単な方法は、dataclassesライブラリの@dataclassを使用することです。これにより、クラスを作成するための多くのボイラープレートコードを記述する必要がなくなります。(たとえば、組み込みの__init____repr__(表現)関数など)。

dataclassデコレータを使用しないクラスの例:

class Car:
    def __init__(self, make: str, model: str, year: int):
        self.make = make
        self.model = model
        self.year = year

    def __repr__(self):
        return f"{self.year} {self.make} {self.model}"

my_car = Car("Toyota", "Corolla", 2020)
print(my_car)  # 出力: 2020 Toyota Corolla

British python devs be like thats a constructor, __init__?

データクラスデコレータを使った例で、上記と同じ結果を得られます(ただし、コード量が少なくなります)。

from dataclasses import dataclass

@dataclass
class Car:
    make: str
    model: str
    year: int

my_car = Car("Toyota", "Corolla", 2020)
print(my_car)  # 出力:Car(make='Toyota', model='Corolla', year=2020)

Easy 課題 1 - クラスを作成する

Person という名前のクラスを作成してください。このクラスは、以下の属性を持つ必要があります。

  • name: 人の名前
  • eye_color: 人の目の色
  • phone_number: 人の電話番号
  • email: 人のメールアドレス

以下の例のように、すべての属性に有効な値を持つ Person クラスのインスタンス(オブジェクト)を作成してください。

@dataclass
class Person:
    ... # ここにあなたのコードを記述してください

bob_kaare = Person(name="Bob Kåre",
                   eye_color="blue",
                   phone_number="12345678",
                   email="bob_kaare@example.com")
print(bob_kaare)

Løsning: En dataclass for Person

ここに可能な解決策を示します:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    eye_color: str
    phone_number: str
    email: str

Medium 課題 2 - クラスでの検証

上記の例では、_検証_を追加していません。つまり、次のような無効な値を持つ Person を作成できます。

@dataclass
class Person:
    ... # ここにあなたのコードを記述してください

invalid_person = Person(name="",
                        eye_color="yes",
                        phone_number="12345",
                        email="not-an-email")
print(invalid_person)
# 出力: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')

これは(潜在的に)問題があり、将来的に技術的負債につながる可能性があります。幸いなことに、クラスに検証を追加する簡単な方法があります。

まず、電子メールの検証を見てみましょう。Pythonには、これを支援できる組み込みライブラリがありますが、学習のためにこれを行っているため、新しい「Email」クラスを作成し、__post_init__関数(dataclassのみ)を調べて、独自の単純な検証を作成します。

Merk

私たちは、これを Person クラス自体内でも行うことができますが、再利用可能なものの場合は、専用のクラスを作成する方が良いことがよくあります。

post_init 関数の例
from dataclasses import dataclass

@dataclass
class Email:
    address: str

    def __post_init__(self):
        print(f"Validating email: {self.address}")
        # ここにコードを記述

単純なメールアドレスの検証を行う場合、例えば、メールアドレスに @. の両方の文字が含まれているかどうかを確認できます。必要に応じて、メールアドレスが正規表現パターンに一致するかどうかを確認することもできます。(より高度ですが、ぜひウェブで検索してみてください!)

Løsning: Kode for enkel e-post validering

ここに可能な解決策を示します。メールアドレスが無効な場合に「クラッシュ」させるために例外を使用します。これにより、プログラムがすぐに停止し、エラーメッセージが表示されます。

from dataclasses import dataclass

@dataclass
class Email:
    address: str

    def __post_init__(self):
        if "@" not in self.address:
            raise ValueError(f"Mangler @ i epostadressen: {self.address}")
        if "." not in self.address.split("@")[1]:
            raise ValueError(f"Mangler . i domenet til epostadressen: {self.address}")
        if " " in self.address:
            raise ValueError(f"Epostadressen kan ikke inneholde mellomrom: {self.address}")

# Test koden 
test = Email("hei@example.com")  # Gyldig
try:
    test = Email("heiexample.com")
except ValueError as e:
    print(e) # Ugyldig, mangler @

Medium 課題 3 - 電話番号の検証

メールアドレスで行ったのと同じように、電話番号用のクラスを作成してください。

電話番号の検証における課題!

電話番号の検証を修正して、文字(str)と数字(int)の両方を受け入れられるようにできますか? 例えば、12345678"12345678"の両方が有効である必要があります。

また、国番号を属性(クラスのサブ値)として追加してみてください。例えば、ノルウェーの47、スウェーデンの46など。

Medium 課題4 - Personクラスで検証を使用する

これで、メールアドレスと電話番号の検証を作成したので、それらを Person クラスで使用できます。

@dataclass
class Person:
    name: str
    eye_color: str
    phone_number: PhoneNumber  # PhoneNumberクラスを使用
    email: Email               # Emailクラスを使用

新しい課題が発生しました!

これで、PersonクラスがPhoneNumberEmailクラスを使用するように変更されたので、Personのインスタンス化(作成)方法も変更する必要があります。 まず、PhoneNumberEmailオブジェクトを作成してから、Personを作成する必要があります。

bob_kaare = Person(name="Bob Kåre",
                   eye_color="blue",
                   phone_number=PhoneNumber("12345678"),  # 変更点に注意してください
                   email=Email("bob_kaare@example.com"))  # 変更点に注意してください
print(bob_kaare)

# 注意:値の取得方法も変更する必要があります
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number)  # .country_code(?)

Hard 課題 5 - クラスのプロパティ (任意)

オブジェクトを使ってメールアドレスや電話番号のような値を表現する場合、値を取得するたびにサブ値 (例: メールアドレスの address、電話番号の number) を指定する必要があります。これは長期的には少し面倒になる可能性があります。幸いなことに、これに対する解決策があり、クラスの @property デコレータを使用することで、サブ値を指定することなく、オブジェクトから直接値を取得できます。

ただし、これにより別の課題が生じます。それは、Person クラスの __init__ 関数が必要になることです。これは、データクラスでプロパティと属性に同じ名前を使用できないためです。

from dataclasses import dataclass

@dataclass
class EksempelVerdi:
    attributt: str

@dataclass
class Person:
    name: str
    _verdi: EksempelVerdi  # 内部変数(_で始まることで「プライベート」であることを示す)

    def __init__(self, name: str, verdi: EksempelVerdi):
        self.name = name
        self._verdi = verdi

    @property
    def verdi(self):
        return self._verdi.attributt  # 下位値を直接取得する

# テストコード
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi)  # 出力: Noe tekst

Merk

プロパティは、パラメータを必要とせず、実行時に括弧を必要としない点でユニークです。例では、person.verdi を括弧なしで取得しています ( person.verdi() ではありません)。これは、技術的には関数であるにもかかわらずです。

代替例で、str と EksempelVerdi の両方を受け入れる
@dataclass
class Person:
    name: str
    _verdi: str

    def __init__(self, name: str, verdi: str | EksempelVerdi):
        self.name = name
        if isinstance(verdi, EksempelVerdi):
            self._verdi = verdi
        elif isinstance(verdi, str):
            self._verdi = EksempelVerdi(verdi)
        else:
            raise TypeError("verdi må være av type str eller EksempelVerdi")

    @property
    def verdi(self) -> str:
        return self._verdi.attributt  # Henter ut underverdien direkte

# テストコード
person = Person(name="Alice", verdi="Noe tekst")
print(person.verdi)  # 出力: Noe tekst

Hard 課題 6 - ロジックを持つプロパティ (オプション)

Person を更新し、birthday (誕生日) という新しい属性を追加します。これは datetime.date 型 ( datetime ライブラリから) である必要があります。

次に、次のプロパティを作成します。

  • birthday と現在の日付に基づいて、その人の年齢を計算する age プロパティを作成します。
  • その人が 18 歳以上の場合に True を返し、それ以外の場合は False を返す is_adult プロパティを作成します。

Løsning: Alder og voksen som properties

ここに可能な解決策を示します:

from dataclasses import dataclass
from datetime import date

@dataclass
class Person:
    name: str
    birthday: date

    @property
    def age(self) -> int:
        """生年月日と現在の日付に基づいて年齢を計算します"""
        today = date.today()
        age = today.year - self.birthday.year
        # まだ今年の誕生日を迎えていない場合は、1減算します
        if (today.month, today.day) < (self.birthday.month, self.birthday.day):
            age -= 1
        return age

    @property
    def is_adult(self) -> bool:
        """その人が18歳以上である場合はTrueを返します"""
        return self.age >= 18

# コードをテストします
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age)       # 例:今日が2023年5月15日以降の場合は18歳
print(person.is_adult)  # True

また別の課題です!

Personのインスタンス化が、datetime.dateと、birthdayのフォーマットが "DD-MM-YYYY" の文字列の両方を受け入れるようにできますか?(ヒント:文字列をdatetime.dateに変換するには、datetime.strptimeを使用してください)