这是一段机器翻译的文本,可能包含错误!
在 Python 中(也适用于大多数其他语言),可以创建自定义对象,具有自己的值、规则和函数。 这称为类(英文为 classes)。 我们使用类将相关数据和函数组合成一个单元,例如一个 Ordre 类,它具有 ordre_id、kunde_navn、produkter 等数据,以及 legg_til_produkt()、beregn_total() 等函数。
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
使用 dataclass 装饰器的示例,达到与上述相同的效果(但代码更少):
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)
任务 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
任务 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}")
# 你的代码在这里
对于简单的电子邮件验证,例如,我们可以检查电子邮件是否同时包含 @ 和 . 字符。或者,您可以检查电子邮件是否与正则表达式模式匹配。(更高级,但请在网上搜索!)
解决方案:简单的电子邮件验证代码
这里有一个可能的解决方案,我们使用异常来“崩溃”,如果电子邮件无效,这将立即停止程序,并显示错误消息。
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"电子邮件地址中缺少 @: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"电子邮件地址的域名中缺少 .: {self.address}")
if " " in self.address:
raise ValueError(f"电子邮件地址不能包含空格: {self.address}")
# 测试代码
test = Email("hei@example.com") # 有效
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # 无效,缺少 @
任务 3 - 电话号码验证
创建与您为电子邮件所做的类似的类,但现在用于电话号码。
电话验证挑战!
你能修复电话号码的验证,使其既能接受字母(字符串)又能接受数字(整数)吗? 例如,12345678 和 "12345678" 都应该是有效的。
另外,尝试将国家代码添加为属性(类的子值)。 例如,47 代表挪威,46 代表瑞典。
任务 4 - 在 Person 类中使用验证
现在我们已经为电子邮件和电话号码创建了验证,我们可以在 Person 类中使用它们。
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # 使用 PhoneNumber 类
email: Email # 使用 Email 类
新挑战出现!
现在我们已经修改了 Person 类以使用 PhoneNumber 和 Email 类,我们也必须修改实例化(创建)Person 的方式。 我们现在必须先创建一个 PhoneNumber 和一个 Email 对象,然后才能创建一个 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(?)
任务 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 必须是 str 或 EksempelVerdi 类型")
@property
def verdi(self) -> str:
return self._verdi.attributt # 直接获取子值
# 测试代码
person = Person(name="Alice", verdi="一些文本")
print(person.verdi) # 输出: 一些文本
任务 6 - 带有逻辑的属性 (可选)
更新 Person,添加一个新的属性,名为 birthday (生日)。 这应该是 datetime.date 类型 (来自 datetime 库)。
然后,创建以下属性:
- 创建一个
age属性,根据birthday和当前日期计算人的年龄。 - 创建一个
is_adult属性,如果人的年龄为 18 岁或以上,则返回True,否则返回False。
解决方案:年龄和成年属性
这里有一个可能的解决方案:
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 和格式为 "DD-MM-YYYY" 的文本作为 birthday? (提示:使用 datetime.strptime 将字符串转换为 datetime.date)
