# Python 类与对象

## 学习目标
- 理解面向对象编程（OOP）的基本概念
- 掌握类的定义和对象的创建
- 理解实例属性、类属性和方法的区别
- 掌握 `__init__` 构造方法和 `self` 的含义

---

![Python面向对象核心](https://img.zhaojq.top/20260730221649813.jpg "Python面向对象核心")

## 一、什么是面向对象编程

面向对象编程（Object-Oriented Programming，OOP）是一种编程范式，将数据和操作数据的方法组织在一起，形成"对象"。

核心概念：
- **类（Class）**：对象的蓝图或模板
- **对象（Object）**：类的实例
- **属性（Attribute）**：对象的数据
- **方法（Method）**：对象的行为

---

## 二、定义类

```python
class Dog:
    """狗类"""

    # 类属性（所有实例共享）
    species = "Canis familiaris"

    # 构造方法（初始化实例）
    def __init__(self, name, age):
        # 实例属性（每个实例独立）
        self.name = name
        self.age = age

    # 实例方法
    def bark(self):
        return f"{self.name} says: 汪汪!"

    def info(self):
        return f"{self.name} 今年 {self.age} 岁"


# 创建对象（实例化）
dog1 = Dog("Buddy", 3)
dog2 = Dog("Luna", 2)

# 访问属性和方法
print(dog1.name)           # Buddy
print(dog1.age)            # 3
print(dog1.bark())         # Buddy says: 汪汪!
print(dog1.info())         # Buddy 今年 3 岁

print(dog2.name)           # Luna
print(dog2.bark())         # Luna says: 汪汪!
```

---

## 三、`__init__` 构造方法

`__init__` 是类的构造方法，在创建对象时自动调用，用于初始化实例属性。

```python
class Person:
    def __init__(self, name, age=18):
        """
        构造方法
        name: 姓名（必需）
        age: 年龄（默认 18）
        """
        self.name = name
        self.age = age
        self._id = None          # 受保护属性（约定俗成）

    def set_id(self, id_number):
        self._id = id_number

    def get_id(self):
        return self._id


# 使用
p1 = Person("Alice", 25)
p2 = Person("Bob")           # 使用默认年龄

print(p1.name, p1.age)       # Alice 25
print(p2.name, p2.age)       # Bob 18
```

---

## 四、self 是什么

`self` 代表类的实例本身，它是实例方法的第一个参数（约定俗成的名称）。

```python
class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1
        return self.count

    def reset(self):
        self.count = 0


c = Counter()
print(c.increment())   # 1
print(c.increment())   # 2
print(c.increment())   # 3

c.reset()
print(c.count)         # 0
```

**等价调用方式**：

```python
# 以下两种调用等价
c.increment()          # 隐式传递 self
Counter.increment(c)   # 显式传递 self
```

---

## 五、类属性 vs 实例属性

```python
class Student:
    # 类属性（所有实例共享）
    school = "北京大学"
    count = 0

    def __init__(self, name):
        # 实例属性（每个实例独立）
        self.name = name
        self.scores = []
        Student.count += 1   # 修改类属性

    def add_score(self, score):
        self.scores.append(score)


# 创建学生
s1 = Student("Alice")
s2 = Student("Bob")

# 访问类属性
print(s1.school)       # 北京大学
print(s2.school)       # 北京大学
print(Student.school)  # 北京大学

# 访问实例属性
print(s1.name)         # Alice
print(s2.name)         # Bob

# 类属性共享
print(Student.count)   # 2（两个实例）

# 修改类属性
Student.school = "清华大学"
print(s1.school)       # 清华大学
print(s2.school)       # 清华大学

# 通过实例修改类属性（危险！会创建实例属性）
s1.school = "复旦大学"  # 这里创建了一个实例属性，不影响类属性
print(s1.school)       # 复旦大学（实例属性）
print(s2.school)       # 清华大学（类属性）
print(Student.school)  # 清华大学
```

---

## 六、类方法、静态方法与实例方法

```python
class MathUtils:
    # 类属性
    PI = 3.14159

    def __init__(self, name):
        self.name = name

    # 实例方法（需要 self）
    def greet(self):
        return f"Hello, {self.name}"

    # 类方法（需要 cls，可访问类属性）
    @classmethod
    def circle_area(cls, radius):
        return cls.PI * radius ** 2

    # 静态方法（不需要 self 或 cls，类似普通函数）
    @staticmethod
    def add(a, b):
        return a + b


# 调用实例方法
m = MathUtils("Alice")
print(m.greet())           # Hello, Alice

# 调用类方法
print(MathUtils.circle_area(5))   # 78.53975
print(m.circle_area(5))           # 也可以

# 调用静态方法
print(MathUtils.add(3, 5))        # 8
print(m.add(3, 5))                # 也可以
```

| 方法类型 | 装饰器 | 第一个参数 | 用途 |
|----------|--------|------------|------|
| 实例方法 | 无 | `self` | 操作实例属性 |
| 类方法 | `@classmethod` | `cls` | 操作类属性、替代构造方法 |
| 静态方法 | `@staticmethod` | 无 | 与类相关的工具函数 |

### 类方法作为替代构造方法

```python
class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_string(cls, date_str):
        """从字符串创建日期对象"""
        year, month, day = map(int, date_str.split("-"))
        return cls(year, month, day)

    @classmethod
    def today(cls):
        """创建今天的日期对象"""
        import datetime
        now = datetime.datetime.now()
        return cls(now.year, now.month, now.day)

    def __str__(self):
        return f"{self.year}-{self.month:02d}-{self.day:02d}"


# 使用替代构造方法
d1 = Date(2024, 1, 15)
d2 = Date.from_string("2024-06-01")
d3 = Date.today()

print(d1)   # 2024-01-15
print(d2)   # 2024-06-01
print(d3)   # 今天的日期
```

---

## 七、访问控制

Python 没有严格的访问控制，使用命名约定：

```python
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self._balance = balance        # 受保护属性（单下划线）
        self.__password = "123456"     # 私有属性（双下划线）

    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
            return True
        return False

    def withdraw(self, amount):
        if 0 < amount <= self._balance:
            self._balance -= amount
            return True
        return False

    def get_balance(self):
        return self._balance

    def _internal_method(self):        # 受保护方法
        pass

    def __private_method(self):        # 私有方法
        pass


account = BankAccount("Alice", 1000)
print(account.owner)       # Alice
print(account._balance)    # 1000（可以访问，但不建议）
# print(account.__password)  # AttributeError!

# 双下划线属性被改名（名称修饰）
print(account._BankAccount__password)   # 123456（不要这样做）

account.deposit(500)
print(account.get_balance())   # 1500
account.withdraw(200)
print(account.get_balance())   # 1300
```

---

## 八、综合示例

```python
"""
图书管理系统
"""

class Book:
    """图书类"""
    total_books = 0

    def __init__(self, title, author, isbn):
        self.title = title
        self.author = author
        self.isbn = isbn
        self._is_borrowed = False
        Book.total_books += 1

    def borrow(self):
        if not self._is_borrowed:
            self._is_borrowed = True
            return f"《{self.title}》借阅成功"
        return f"《{self.title}》已被借出"

    def return_book(self):
        if self._is_borrowed:
            self._is_borrowed = False
            return f"《{self.title}》归还成功"
        return f"《{self.title}》未被借出"

    def get_status(self):
        status = "已借出" if self._is_borrowed else "在馆"
        return f"《{self.title}》- {status}"

    def __str__(self):
        return f"《{self.title}》by {self.author} (ISBN: {self.isbn})"


class Library:
    """图书馆类"""

    def __init__(self, name):
        self.name = name
        self.books = {}

    def add_book(self, book):
        self.books[book.isbn] = book
        return f"已添加 {book.title}"

    def find_book(self, isbn):
        return self.books.get(isbn)

    def list_books(self):
        print(f"\n=== {self.name} 藏书列表 ===")
        for book in self.books.values():
            print(f"  {book} - {book.get_status()}")


# 使用
lib = Library("城市图书馆")

book1 = Book("Python编程", "张三", "978-1")
book2 = Book("数据结构", "李四", "978-2")
book3 = Book("算法导论", "王五", "978-3")

lib.add_book(book1)
lib.add_book(book2)
lib.add_book(book3)

lib.list_books()

print(f"\n{book1.borrow()}")
print(f"{book2.borrow()}")

lib.list_books()

print(f"\n{book1.return_book()}")
print(f"\n图书馆总藏书: {Book.total_books}")
```

---

## 小结

- 类是对象的模板，使用 `class` 关键字定义
- `__init__` 是构造方法，用于初始化实例属性
- `self` 代表实例本身，是实例方法的第一个参数
- 类属性被所有实例共享，实例属性每个实例独立
- `@classmethod` 定义类方法，`@staticmethod` 定义静态方法
- Python 使用单下划线 `_` 表示受保护，双下划线 `__` 表示私有（名称修饰）

---

## 练习

1. 定义一个 `Rectangle` 类，包含长和宽属性，以及计算面积和周长的方法。
2. 定义一个 `Circle` 类，使用类属性存储 PI，提供计算面积和 circumference 的方法。
3. 编写一个 `BankAccount` 类，支持存款、取款、查询余额，余额不能为负数。
4. 使用 `@classmethod` 为 `Book` 类添加一个从字典创建实例的方法。
5. 解释 `self` 和 `cls` 的区别，并举例说明。

