Python Day14 类的封装 及 例题分享
一、封装特性
封装是面向对象编程的重要特性,在 Python 中主要通过以下方式实现:
- 属性私有化
- 受保护属性:属性名以单下划线
_开头,这类属性可在本类或子类中使用,不过这更多是一种编程约定。 - 私有属性:属性名以双下划线
__开头,它只能在当前类内部被访问。实际上,Python 会将其重命名为_类名__属性名的形式,虽然可以通过这种重命名后的名称在外部访问私有属性,但不建议这么做。
- 受保护属性:属性名以单下划线
- 示例代码
class Cat: def __init__(self, name, age): self.__name = name # 私有属性 self._age = age # 受保护属性
二、property 属性
1. 基本概念
property 属性的作用是将属性的获取、修改和删除方法整合为一个属性,让属性操作能像普通字段一样简洁,同时隐藏内部的实现细节。
2. 两种实现方式
方式一:使用 property 类
class Cat:
def __init__(self, age):
self.__age = age
def get_age(self):
return self.__age
def set_age(self, age):
if 0 <= age <= 20:
self.__age = age
else:
raise ValueError("年龄需在0-20之间")
age = property(get_age, set_age)
方式二:使用装饰器
class Dog:
def __init__(self, age):
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if 0 <= age <= 20:
self.__age = age
else:
raise ValueError("年龄需在0-20之间")
三、静态方法和类方法
1. 静态方法
静态方法使用@staticmethod装饰器来标记,它与类和对象都没有直接关联,其第一个参数不需要是self或cls。静态方法通常用于封装与类相关的工具函数。
class Calc:
@staticmethod
def add(a, b):
return a + b
2. 类方法
类方法使用@classmethod装饰器来标记,它的第一个参数通常是cls,代表类本身。类方法常被用于实现工厂模式或操作类属性。
class Calc:
@classmethod
def add(cls, a, b):
return a + b
四、成员属性与类属性
1. 成员属性
成员属性在__init__方法中定义,它与对象相关,每个对象的成员属性都是独立的。
class Book:
def __init__(self, title):
self.title = title # 成员属性
2. 类属性
类属性直接定义在类中,它被所有对象共享。当通过对象访问类属性时,如果对象没有同名的成员属性,就会访问类属性;而当通过对象对类属性进行赋值操作时,实际上会创建一个同名的成员属性。
class Book:
count = 0 # 类属性
def __init__(self):
Book.count += 1
五、集合去重原理
1. set 集合去重机制
set 集合去重依赖于对象的__hash__和__eq__方法。如果两个对象的哈希值相同,并且通过__eq__方法比较结果也相同,那么 set 集合会将它们视为同一个对象,从而实现去重。
2. 示例代码
class Fish:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other.name and self.age == other.age
def __hash__(self):
return hash((self.name, self.age))
六、注意事项
- 访问私有属性:在类外部可以通过
_类名__属性名的方式访问私有属性,但这种方式破坏了类的封装性,不建议使用。 - 类方法与静态方法的调用:类方法和静态方法都可以通过类名直接调用,不过静态方法也能通过对象调用,只是不推荐这么做。
- 属性查找顺序:当通过对象访问属性时,会先查找成员属性,若没有则查找类属性;而对属性进行赋值操作时,会优先创建或修改成员属性。
一、圆形类(Circle)
需求:定义圆形类,私有化半径属性,实现周长、面积计算;创建对象时半径可缺省(默认 0)。
class Circle:
def __init__(self, radius=0):
self.radius = radius # 调用setter方法
# 计算面积
def circle_s(self):
return 3.14 * self.radius **2
# 计算周长
def circle_c(self):
return 2 * 3.14 * self.radius
# 半径属性(私有化访问)
@property
def radius(self):
return self.__radius
@radius.setter
def radius(self, radius):
if radius < 0:
raise ValueError("半径不可能为负数!")
self.__radius = radius
@radius.deleter
def radius(self):
del self.__radius
# 测试代码
if __name__ == '__main__':
try:
c1 = Circle(-1) # 触发异常
except ValueError as e:
print(e) # 输出:半径不可能为负数!
c2 = Circle(2)
print(f"半径为2的圆面积:{c2.circle_s()}") # 输出:12.56
print(f"半径为2的圆周长:{c2.circle_c()}") # 输出:12.56
c3 = Circle() # 缺省半径(默认0)
print(f"默认半径的圆面积:{c3.circle_s()}") # 输出:0.0
二、小说类(Novel)
需求:私有化作者、小说名、类型、价格属性;实现列表排序(按价格降序)和集合去重(按小说名 + 作者)。
class Novel:
def __init__(self, author, name, genre, price):
self.author = author
self.name = name
self.genre = genre
self.price = price
# 用于判断对象是否相等(去重依据)
def __eq__(self, other):
if not isinstance(other, Novel):
return False
return self.name == other.name and self.author == other.author
# 用于集合去重(哈希值依据)
def __hash__(self):
return hash((self.name, self.author))
# 打印对象时显示的信息
def __repr__(self):
return f"Novel(作者:{self.author}, 书名:{self.name}, 类型:{self.genre}, 价格:{self.price})"
# 私有属性的property访问
@property
def author(self):
return self.__author
@author.setter
def author(self, author):
self.__author = author
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
@property
def genre(self):
return self.__genre
@genre.setter
def genre(self, genre):
self.__genre = genre
@property
def price(self):
return self.__price
@price.setter
def price(self, price):
self.__price = price
# 测试代码
if __name__ == '__main__':
# 1. 创建5本小说并存储到列表
novels = [
Novel("金庸", "射雕英雄传", "武侠", 59.8),
Novel("金庸", "神雕侠侣", "武侠", 69.8),
Novel("古龙", "陆小凤传奇", "武侠", 49.9),
Novel("金庸", "射雕英雄传", "武侠", 59.8), # 与第一本重复(书名+作者相同)
Novel("JK罗琳", "哈利波特", "魔幻", 89.0)
]
# 按价格降序排序
novels_sorted = sorted(novels, key=lambda x: x.price, reverse=True)
print("按价格降序排序后的小说:")
for novel in novels_sorted:
print(novel)
# 2. 存储到集合(自动去重)
novels_set = set(novels)
print("\n去重后的小说集合:")
for novel in novels_set:
print(novel)
三、工具类(QikuxUtils)
需求:实现数字计算、闰年判断、手机号 / 邮箱验证及脱敏等静态方法。
import re # 用于正则表达式验证
class QikuxUtils:
# a) 计算多个数字的和(至少1个数字)
@staticmethod
def sum(a, *args):
total = a
for num in args:
total += num
return total
# b) 计算多个数字的最大值
@staticmethod
def max(a, *args):
current_max = a
for num in args:
if num > current_max:
current_max = num
return current_max
# c) 计算多个数字的最小值
@staticmethod
def min(a, *args):
current_min = a
for num in args:
if num < current_min:
current_min = num
return current_min
# d) 判断是否为闰年
@staticmethod
def is_leap_year(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
# e) 验证手机号(11位数字,以1开头,第二位为3/4/5/6/7/8/9)
@staticmethod
def is_tel(string):
return len(string) == 11 and string.isdigit() and string[0] == '1' and string[1] in '3456789'
# f) 验证邮箱(简化版:包含@和.,且格式合法)
@staticmethod
def is_email(string):
# 正则表达式:用户名@域名.后缀(简化验证)
pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
return re.match(pattern, string) is not None
# g) 手机号脱敏(中间4位用****代替)
@staticmethod
def tel_secure(string):
if QikuxUtils.is_tel(string):
return f"{string[:3]}****{string[7:]}"
return string
# h) 邮箱脱敏(账号保留第一个字符,其余用6个*代替)
@staticmethod
def email_secure(string):
if QikuxUtils.is_email(string):
username, domain = string.split('@')
return f"{username[0]}******@{domain}"
return string
# i) 判断是否为偶数
@staticmethod
def is_even(num):
return num % 2 == 0
# j) 判断是否为奇数
@staticmethod
def is_odd(num):
return num % 2 != 0
# 测试代码
if __name__ == "__main__":
# 测试数字计算
print("sum(1,2,3) =", QikuxUtils.sum(1,2,3)) # 6
print("max(5,1,9) =", QikuxUtils.max(5,1,9)) # 9
# 测试闰年
print("2024是否闰年?", QikuxUtils.is_leap_year(2024)) # True
# 测试手机号
tel = "13812345678"
print(f"手机号{tel}是否合法?", QikuxUtils.is_tel(tel)) # True
print("手机号脱敏:", QikuxUtils.tel_secure(tel)) # 138****5678
# 测试邮箱
email = "test@example.com"
print(f"邮箱{email}是否合法?", QikuxUtils.is_email(email)) # True
print("邮箱脱敏:", QikuxUtils.email_secure(email)) # t******@example.com
四、列表工具类(ListUtils)
需求:实现列表过滤、映射、查找等静态方法。
from typing import List, Any, Callable
class ListUtils:
# 过滤满足条件的元素(返回新列表)
@staticmethod
def filter(array: List[Any], predicate: Callable[[Any, int], bool]) -> List[Any]:
return [x for i, x in enumerate(array) if predicate(i, x)]
# 按规则映射元素(返回新列表)
@staticmethod
def map(array: List[Any], functional: Callable[[Any, int], Any]) -> List[Any]:
return [functional(i, x) for i, x in enumerate(array)]
# 查找第一个满足条件的元素(无则返回None)
@staticmethod
def find(array: List[Any], predicate: Callable[[Any, int], bool]) -> Any:
for i, x in enumerate(array):
if predicate(i, x):
return x
return None
# 查找第一个满足条件的元素索引(无则返回-1)
@staticmethod
def index(array: List[Any], predicate: Callable[[Any, int], bool]) -> int:
for i, x in enumerate(array):
if predicate(i, x):
return i
return -1
# 列表扁平化(嵌套列表展开)
@staticmethod
def flat(array: List[Any]) -> List[Any]:
result = []
for item in array:
if isinstance(item, list):
result.extend(ListUtils.flat(item)) # 递归处理嵌套列表
else:
result.append(item)
return result
# 测试代码
if __name__ == "__main__":
ls = [1, 2, [3, 4, [5]], 6]
# 测试flat
print("扁平化列表:", ListUtils.flat(ls)) # [1,2,3,4,5,6]
# 测试filter(保留偶数)
print("过滤偶数:", ListUtils.filter([1,2,3,4], lambda i,x: x%2==0)) # [2,4]
# 测试map(元素乘以2)
print("元素乘2:", ListUtils.map([1,2,3], lambda i,x: x*2)) # [2,4,6]
五、猫类(Cat)
需求:私有化名字和年龄;实现对象比较(年龄)、集合去重(名字 + 年龄)。
class Cat:
def __init__(self, name, age):
self.name = name
self.age = age
# 判断对象是否相等(名字+年龄相同)
def __eq__(self, other):
if not isinstance(other, Cat):
return False
return self.name == other.name and self.age == other.age
# 集合去重的哈希值(名字+年龄)
def __hash__(self):
return hash((self.name, self.age))
# 比较年龄大小
def __gt__(self, other): # 大于
return self.age > other.age
def __lt__(self, other): # 小于
return self.age < other.age
# 打印对象信息
def __repr__(self):
return f"Cat(名字:{self.name}, 年龄:{self.age})"
# 私有属性的property访问
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if age < 0:
raise ValueError("年龄不能为负数")
self.__age = age
# 测试代码
if __name__ == "__main__":
cat1 = Cat("Tom", 3)
cat2 = Cat("Tom", 3) # 与cat1相同(名字+年龄)
cat3 = Cat("Jerry", 2)
# 测试对象相等
print("cat1 == cat2?", cat1 == cat2) # True
print("cat1 > cat3?", cat1 > cat3) # True(3>2)
# 测试集合去重
cats = {cat1, cat2, cat3}
print("集合中的猫(去重后):", cats) # 仅保留cat1和cat3
六、日历类(Calendar)
需求:私有化年、月、日;实现日历显示、星期计算、闰年判断等功能。
class Calendar:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# a) 显示当月日历
def show_calendar(self):
max_day = self.get_month_max_day() # 当月最大天数
first_week = self.__get_first_day_week() # 当月1日是星期几(0=周日)
print(f"\n{self.year}年{self.month}月日历")
print("日 一 二 三 四 五 六") # 表头
# 打印第一行的空格(1日之前的空位)
for _ in range(first_week):
print(" ", end=" ")
# 打印日期
for day in range(1, max_day + 1):
# 格式化日期(占3位,右对齐)
print(f"{day:2d} ", end=" ")
# 每7个日期换行(周日开始)
if (first_week + day) % 7 == 0:
print()
print()
# 辅助方法:获取当月1日的星期(用于日历显示)
def __get_first_day_week(self):
temp = Calendar(self.year, self.month, 1)
return temp.get_week_num()
# b) 蔡勒公式获取星期(返回(0, "周日"))
def get_week(self):
y, m, d = self.year, self.month, self.day
# 蔡勒公式适配:1-2月视为上一年的13-14月
if m < 3:
m += 12
y -= 1
c = y // 100 # 世纪数
y = y % 100 # 年份后两位
# 蔡勒公式:w = (d + [13(m+1)/5] + y + [y/4] + [c/4] + 5c) % 7
w = (d + (13 * (m + 1)) // 5 + y + y // 4 + c // 4 + 5 * c) % 7
# 转换为0=周日,1=周一...6=周六
w = (w + 1) % 7 if w != 0 else 0
week_str = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][w]
return (w, week_str)
# c) 判断是否为闰年
def is_leap(self):
return (self.year % 4 == 0 and self.year % 100 != 0) or (self.year % 400 == 0)
# d) 获取当月最大天数
def get_month_max_day(self):
# 大月(31天)、小月(30天)、二月(28/29天)
if self.month in [1,3,5,7,8,10,12]:
return 31
elif self.month in [4,6,9,11]:
return 30
else: # 二月
return 29 if self.is_leap() else 28
# e) 获取当前日期是当年第几天
def get_day_in_year(self):
days = 0
# 累加前几个月的天数
for m in range(1, self.month):
temp = Calendar(self.year, m, 1)
days += temp.get_month_max_day()
# 加当月天数
days += self.day
return days
# f) 获取星期数字(0=周日)
def get_week_num(self):
return self.get_week()[0]
# g) 获取星期字符串(如"周日")
def get_week_str(self):
return self.get_week()[1]
# 私有属性的property访问
@property
def year(self):
return self.__year
@year.setter
def year(self, year):
self.__year = year
@property
def month(self):
return self.__month
@month.setter
def month(self, month):
if not 1 <= month <= 12:
raise ValueError("月份必须在1-12之间")
self.__month = month
@property
def day(self):
return self.__day
@day.setter
def day(self, day):
if not 1 <= day <= self.get_month_max_day():
raise ValueError(f"日期必须在1-{self.get_month_max_day()}之间")
self.__day = day
# 测试代码
if __name__ == '__main__':
cal = Calendar(2024, 2, 29) # 2024是闰年,2月有29天
print(f"测试日期:{cal.year}年{cal.month}月{cal.day}日")
print("是否闰年:", cal.is_leap()) # True
print("当月最大天数:", cal.get_month_max_day()) # 29
print("是当年第几天:", cal.get_day_in_year()) # 31(1月)+29(2月)=60
print("星期:", cal.get_week()) # 例如(4, "周五")
cal.show_calendar() # 显示2024年2月日历
以上代码均包含完整的类定义、方法实现及测试逻辑,可直接运行验证功能。每个类的核心逻辑已通过注释说明,便于理解设计思路。
更多推荐
所有评论(0)