Mooreの小站

Moore的个人小站

Python 第二节

2025-03-25

一、面向对象编程

1、几个概念

  • 面向对象、面向过程

  • 对象

  • 属性、方法

  • 封装

  • 继承

  • 多态

编程中需要先有类才能有对象,然后有对象去对象

这几个看起来拥有相同的特征和行为→归为同一类→类→具有相同的属性和方法

2、创建一个类和实例化对象

  • 通过关键字class进行创建的

  • 通过类名()实例化出一个对象

# 示例
class Example: # 类名大驼峰命名
    def __init__(self):
        self.test = "test"

example = Example()
print(example.test) # 输出test

3、调用/添加属性和方法

(1)调用属性

  • 类内部调用

    • self.属性名

  • 类外部调用

    • 对象名.属性名

class Person:
    def __init__(self, name):
        self.name = name  # 类内部添加属性
    def test(self):
        print(self.name) # 类内部调用属性

p = Person("Alice")
# 类外部调用属性
print(p.name)  # 输出: Alice
p.test() # 输出: Alice

(2)添加属性

  • 类内部添加

    • self.属性名 = 值

  • 类外部添加

    • 对象名.属性名 = 值

class DynamicPerson:
    def __init__(self):
        self.base_attr = "基础属性"

    def add_attr(self, key, value):
        self.__dict__[key] = value  # 扩展:通过__dict__内部动态添加属性
        self.name = "张三" # 类内部直接添加属性

dp = DynamicPerson()
dp.add_attr("age", 25)
print(dp.age)  # 输出: 25
print(dp.name) # 输出:张三

# 类外部直接添加属性
dp.gender = "女"
print(dp.gender)  # 输出: 女

(3)调用方法

  • 类内部调用方法

    • self.方法名()

  • 类外部调用方法

    • 对象名.方法名()

class Calculator:
    def add(self, a, b):
        return a + b  # 类内部定义方法

    def test(self):
        result = self.add(3, 5) # 类内部调用方法
        print(result)  # 输出: 8

clac = Calculator()
result = clac.add(3, 5)  # 类外部调用方法
print(result)  # 输出: 8

(4)添加方法

  • 类内部添加

    • def 方法名():

  • 类外部添加

    • 对象名.方法名 = 方法名

import types # 导入types模块

class Calculator:
    def add(self, a, b):
        return a + b

    def test(self): # 类内部添加方法
        return "test"

def example(self):
    return "example"

# 类外部直接添加方法
Calculator.example = example

def dynamic_method(self):
    return "method"

calc = Calculator()
# 扩展:通过types.MethodType动态绑定方法
calc.dynamic_method = types.MethodType(dynamic_method, calc)

print(calc.dynamic_method())  # 输出 method
print(calc.test()) # 输出 test
print(calc.example()) # 输出 example

# 请注意:动态绑定只适用于当前实例
new_calc = Calculator()
print(new_calc.test()) # 输出 test
print(new_calc.example()) # 输出 example
print(new_calc.dynamic_method())  # 将报错AttributeError

4、魔法方法

  • __xxx__() 以两个下划线开头和结尾的系统自带的方法

常用方法

  • __init__:类实例化对象的时候会自动调用

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age  # 初始化实例属性

p = Person("张三", 25)
  • __str__:重写打印自己的方法,必须return一个字符串

class Book:
    def __init__(self, title):
        self.title = title

    def __str__(self):
        return f"《{self.title}》是一本好书"  # 自定义打印输出

book = Book("Python编程")
print(book)  # 输出: 《Python编程》是一本好书
  • __del__:对象被删除/释放的时候会自动调用,使用del关键字进行删除,此时如果再调用该方法,则会报错没有定义该对象

class FileHandler:
    def __init__(self, filename):
        self.file = open(filename, 'w')

    def __del__(self):
        self.file.close()  # 自动关闭文件

handler = FileHandler("test.txt")
# 当handler对象被销毁时,文件自动关闭
  • __dict__:用来显示对象所有属性和的对应的属性值

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

emp = Employee("李四", 10000)
print(emp.__dict__)  # 输出: {'name': '李四', 'salary': 10000}
  • __main__:用来定义程序的主入口

  • __name__:Python内置的一个全局属性,用于主程序判断

def main():
    print("程序主入口")

if __name__ == "__main__":  # 判断是否直接运行该文件
    main()

二、类的封装

1、私有化

  • 属性私有化:变量名前加__(名称改写)

    class MyClass:
        def __init__(self):
            self.__private = 10  # 私有属性
  • 方法私有化:同样加__,避免被子类覆盖

    def __private_method(self): ...

2、getter, setter

  • 传统写法

    class Person:
        def __init__(self, name):
            self.__name = name  # 私有属性
    
        # 方法名可以随意取,但通常为了方便理解取名为get_属性名,set_属性名
        def get_name(self):  # Getter
            return self.__name
    
        def set_name(self, name):  # Setter(带验证)
            if len(name) < 2:
                raise ValueError("姓名≥2字符")
            self.__name = name
  • @property装饰器

    class Rectangle:
        def __init__(self, w, h):
            self.__w = w
            self.__h = h
    
        @property  # Getter
        # 通过@属性名.getter这个装饰器也可以获取属性值
        # 但因为有@property本身具备Getter功能,可忽略
        def area(self):
            return self.__w * self.__h
    
        @area.setter  # Setter
        # 通过@属性名.setter这个装饰器给属性赋值
        def area(self, val):
            if val <= 0:
                raise ValueError("面积>0")
            self.__w = val ​** 0.5
            self.__h = val ​** 0.5

三、继承

1、单继承

  • 语法

class 父类:  
    def method(self):  
        print("父类方法")

class 子类(父类):  # 单继承
    def method(self):  # 方法覆盖
        super().method()  # 调用父类方法
        print("子类方法")

(1)方法覆盖

  • 子类重新实现父类方法

class User():
    def __init__(self):
        self.name = 'zhnagsan'
class Student(User): 
    def __init__(self):
        self.name = 'lisi' 
#此时子类覆盖了父类的方法,将输出lisi

(2)方法扩展

  • 在父类方法基础上新增功能

class User():
    def __init__(self):
        self.name = 'zhnagsan'
class Student(User): 
    def test(self):
        super().__init__() # 调用父类方法,输出zhangsan
        print("Hello") # 扩展输出Hello

2、多继承

  • 语法

class 父类A:  
    def method_a(self):  
        print("父类A方法")

class 父类B:  
    def method_b(self):  
        print("父类B方法")

class 子类(父类A, 父类B):  # 多继承
    pass  # 不写任何代码,使用pass跳过

需要注意的点

  1. 如果两个父类的__init__中有同名的属性,那么子类调用的时候用的是第一个父类的属性

class User():
    def __init__(self):
        self.name = 'zhangsan'

class Person():
    def __init__(self):
        self.name = "wangwu"

class Student(Person, User):
    pass

stu = Student()
print(stu.name) # 输出wangwu
  • 在这个例子中,Student 类继承了 PersonUser 这两个父类。当创建 Student 类的实例 stu 时,Python 会调用 Student 类的 __init__ 方法。由于 Student 类没有定义自己的 __init__ 方法,Python 会按照 MRO 顺序去查找 __init__ 方法。这里的 MRO 顺序是 [Student, Person, User, object],所以 Python 会先调用 Person 类的 __init__ 方法,这样 stu.name 的值就会被设为 "wangwu",从而忽略 User 类的 __init__ 方法。

  1. 如果两个父类的__init__中有有不同的属性,那么子类只能调用第一个父类的属性,调用第二个属性会报错

class User():
    def __init__(self):
        self.name = 'zhangsan'

class Person():
    def __init__(self):
        self.age = 18

class Student(User, Person):
    pass

stu = Student()
print(stu.name) # 输出zhangsan
print(stu.age) # 报错AttributeError
  • 在这个例子里,Student 类同样继承了 UserPerson 两个父类。创建 Student 类的实例 stu 时,Python 按照 MRO 顺序 [Student, User, Person, object] 查找 __init__ 方法,首先会调用 User 类的 __init__ 方法,从而 stu 实例会有 name 属性,其值为 "zhangsan"。但由于没有调用 Person 类的 __init__ 方法,stu 实例就不会有 age 属性,且 User 类没有设置 age 属性,所以当你尝试访问 stu.age 时,就会抛出 AttributeError 异常。

  • 解决方案:

    • 方案1:显式调用父类的__init__方法

      class student(User, Person):
          def __init__(self):
              User.__init__(self)
              Person.__init__(self)
    • 方案2:使用super()函数

      class student(User, Person):
          def __init__(self):
              super().__init__()  # 通过Super()调用第一个父类→User类的__init__方法
              Person.__init__(self)  # 再显式调用Person类的__init__方法

这两个问题的原因都与Python的多继承机制有关,准确讲是与方法解析顺序(Method Resolution Order,MRO)和构造函数的调用方式有关,Python 采用 C3 线性化算法来确定 MRO,这意味着在查找属性时,Python 会按照特定的顺序去遍历父类。

四、课后复习

1、扩展:OpenCV人脸识别加密

  • 人脸识别:使用级联分类器

  • openCV自带的分类器(可上git网站查看),可以识别人脸,识别眼睛,识别微笑,识别猫脸

  • 步骤:

    1. 导入分类器:CascadeClassifier()

    2. 检测

    • 将背景图进行灰度化

    • 进行检测,得到人脸数据faces

    • 遍历faces,全出每一张人脸的矩形框,并将对应加密后的位置覆盖上去

import cv2
import numpy

# 加载分类器
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
# 读取原图
original_img = cv2.imread('images/face.png')
# 将图片进行灰度化
gray_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY)
# 进行人脸检测,返回的是二维列表,记录了多张人脸的位置
faces = face_cascade.detectMultiScale(
    gray_img,  # 要检测的图片
    scaleFactor=1.01,  # 图片缩放比例
    minNeighbors=18,  # 控制人脸检测的误检率
    minSize=(8, 8)  # 人脸的最小尺寸
)
# 获取原图数据
r, c, channels = original_img.shape
# 获取key
key = numpy.random.randint(0, 256, size=(r, c, channels), dtype=numpy.uint8)
# 异或进行加密
encryption_img = cv2.bitwise_xor(original_img, key)
# 遍历列表,替换加密数据和画框
for x, y, w, h in faces:
    cv2.rectangle(original_img, (x, y), (x + w, y + h), (0, 0, 255), 2)
    original_img[y:y + h, x:x + w] = encryption_img[y:y + h, x:x + w]
# 显示最终图片
cv2.imshow('faces', original_img)
cv2.waitKey()
cv2.destroyAllWindows()

2、扩展:面向对象版学生管理系统

class Student:
    def __init__(self, name, phone, email):
        self.name = name
        self.phone = phone
        self.email = email

    def __str__(self):
        return f"姓名:{self.name}--电话:{self.phone}--邮箱:{self.email}"


class StudentManager:
    def __init__(self):
        self.students = []

    def add_student(self, student):
        self.students.append(student)
        print(f"\n学员 {student.name} 信息添加成功!")

    def remove_student(self, name):
        self.students = [s for s in self.students if s.name != name]
        print(f"\n学员 {name} 信息已删除!")

    def update_student(self, name, new_info):
        for s in self.students:
            if s.name == name:
                s.phone = new_info.get('phone', s.phone)
                s.email = new_info.get('email', s.email)
                print(f"\n学员 {name} 信息已更新!")
                return
        print(f"\n未找到学员 {name}!")

    def sort_students(self):
        self.students.sort(key=lambda x: x.name)
        print("\n学员信息已按姓名排序!")

    def search_students(self, keyword):
        return [s for s in self.students if keyword.lower() in s.name.lower()]

    def export_to_excel(self):
        # 模拟导出功能(实际需调用Excel库)
        print("\n学员信息已导出到Excel!")
        for s in self.students:
            print(s)


class StudentView:
    def display_menu(self):
        print("\n" + "*" * 40)
        print("欢迎使用[学生管理系统]V1.0\n")
        print("1. 新建学员信息")
        print("2. 显示所有学员")
        print("3. 模糊查询学员")
        print("4. 删除学员")
        print("5. 修改学员信息")
        print("6. 按姓名排序")
        print("7. 导出学员数据")
        print("0. 退出系统")
        print("\n" + "*" * 40)

    def get_input(self):
        while True:
            try:
                choice = int(input("\n请输入操作编号: "))
                return choice
            except ValueError:
                print("\n输入错误,请输入数字0-7!")

    def main_loop(self, manager):
        while True:
            self.display_menu()
            choice = self.get_input()

            if choice == 1:
                name = input("请输入姓名: ").strip()
                phone = input("请输入电话: ").strip()
                email = input("请输入邮箱: ").strip()
                student = Student(name, phone, email)
                manager.add_student(student)
            elif choice == 2:
                if not manager.students:
                    print("当前没有学员信息")
                else:
                    for s in manager.students:
                        print(s)
            elif choice == 3:
                keyword = input("请输入查询关键字: ").strip().lower()
                results = manager.search_students(keyword)
                if not results:
                    print(f"未找到包含 '{keyword}' 的学员")
                else:
                    for s in results:
                        print(s)
            elif choice == 4:
                name = input("请输入要删除的学员姓名: ").strip()
                manager.remove_student(name)
            elif choice == 5:
                name = input("请输入要修改的学员姓名: ").strip()
                new_phone = input("请输入新电话(留空保留原值): ").strip()
                new_email = input("请输入新邮箱(留空保留原值): ").strip()
                new_info = {'phone': new_phone, 'email': new_email}
                manager.update_student(name, new_info)
            elif choice == 6:
                manager.sort_students()
            elif choice == 7:
                manager.export_to_excel()
            elif choice == 0:
                print("\n感谢使用,再见!")
                break
            else:
                print("\n无效操作,请重新输入!")


if __name__ == "__main__":
    manager = StudentManager()
    view = StudentView()
    view.main_loop(manager)