嘎嘎嘎嘎嘎今天知道了黑客松,实在是太燃了,这比挑战杯什么老牌的比赛更重视技术,非常燃啊!!!!
好了,白天不知道在摸什么鱼,冲啊,好想看科技会展啊,感觉是很新的科技展会,太有活力了!(7.7写,果然是还没“上班”的活力劲头)
干了两天活,做志愿者“上班”做老师,纯纯牛马,这让我无比珍惜现在的大学生身份。发现自然流出扑克脸一样班味表情,丧失和人沟通分享欲望变成了很寻常的事情,一种模拟体验社会毒打的暑假实践......
1 Python 面向对象编程知识笔记
一、类与对象基础
-
类的定义
- 语法:
class ClassName:
- 类是对象的模板,包含属性(变量)和方法(函数)。
- 示例:
python
运行
class Car: wheels = 4 # 类属性(所有实例共享)
- 语法:
-
对象实例化
- 通过调用类创建对象:
obj = ClassName()
- 每个对象独立拥有实例属性。
- 示例:
python
运行
my_car = Car() print(my_car.wheels) # 输出: 4
- 通过调用类创建对象:
-
__init__
构造方法- 初始化对象属性,创建对象时自动调用。
- 第一个参数必须是
self
,指向实例本身。 - 示例:
python
运行
class Car: def __init__(self, color, brand): self.color = color # 实例属性 self.brand = brand
二、属性与方法类型
-
实例属性与方法
- 实例属性:每个对象独有的数据(通过
self
访问)。 - 实例方法:操作实例数据的函数,第一个参数为
self
。 - 示例:
python
运行
class Car: def __init__(self, speed): self.speed = speed # 实例属性 def accelerate(self, increment): self.speed += increment # 实例方法
- 实例属性:每个对象独有的数据(通过
-
类属性与类方法
- 类属性:所有对象共享的属性(定义在类内部,方法外部)。
- 类方法:操作类属性的函数,使用
@classmethod
装饰器,第一个参数为cls
。 - 示例:
python
运行
class Car: total_cars = 0 # 类属性 @classmethod def add_car(cls): cls.total_cars += 1 # 类方法
-
静态方法
- 不依赖类或实例的工具方法,使用
@staticmethod
装饰器。 - 示例:
python
运行
class Car: @staticmethod def convert_mph_to_kph(mph): return mph * 1.609 # 静态方法
- 不依赖类或实例的工具方法,使用
三、继承与多态
-
单继承
- 子类继承父类的属性和方法。
- 语法:
class Child(Parent):
- 示例:
python
运行
class Vehicle: def move(self): print("Moving...") class Car(Vehicle): # 继承 Vehicle pass
-
方法重写
- 子类覆盖父类的方法。
- 示例:
python
运行
class Car(Vehicle): def move(self): print("Driving...") # 重写父类方法
-
多继承
- 子类继承多个父类的属性和方法。
- 语法:
class Child(Parent1, Parent2):
- 注意:可能引发菱形继承问题(通过 MRO 解决)。
- 示例:
python
运行
class Flyable: def fly(self): print("Flying...") class FlyingCar(Vehicle, Flyable): # 多继承 pass
-
多态
- 不同子类对同一方法的不同实现。
- 示例:
python
运行
for vehicle in [Vehicle(), Car()]: vehicle.move() # 输出不同结果
四、封装与访问控制
-
私有属性与方法
- 使用双下划线
__
前缀定义私有成员(外部无法直接访问)。 - 示例:
python
运行
class BankAccount: def __init__(self, balance): self.__balance = balance # 私有属性 def __withdraw(self, amount): # 私有方法 self.__balance -= amount
- 使用双下划线
-
属性访问控制
- 使用
@property
装饰器创建只读属性。 - 使用
@attr.setter
装饰器创建可写属性。 - 示例:
python
运行
class Circle: def __init__(self, radius): self.__radius = radius @property def radius(self): return self.__radius @radius.setter def radius(self, value): if value > 0: self.__radius = value
- 使用
五、特殊方法(魔术方法)
-
对象表示
__str__()
:返回对象的用户友好字符串表示(str()
调用)。__repr__()
:返回对象的开发者友好字符串表示(调试时使用)。- 示例:
python
运行
class Point: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return f"({self.x}, {self.y})"
-
运算符重载
__add__()
:定义+
运算符行为。__len__()
:定义len()
函数行为。- 示例:
python
运行
class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y)
-
对象比较
__eq__()
:定义==
运算符行为。__lt__()
:定义<
运算符行为。- 示例:
python
运行
class Person: def __init__(self, age): self.age = age def __eq__(self, other): return self.age == other.age
六、高级特性
-
抽象基类(ABC)
- 定义接口,强制子类实现特定方法。
- 使用
abc
模块的@abstractmethod
装饰器。 - 示例:
python
运行
from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass
-
Mixin 类
- 提供特定功能的小型类,用于多重继承。
- 示例:
python
运行
class LoggableMixin: def log(self): print(f"Logging: {self.__class__.__name__}")
-
类的动态特性
- 动态添加属性和方法:
python
运行
class MyClass: pass obj = MyClass() obj.new_attr = 42 # 动态添加属性
- 动态添加属性和方法:
七、实例与类的区别
特性 | 实例 | 类 |
---|---|---|
数据存储 | 存储对象特定的数据 | 存储所有实例共享的数据 |
访问方式 | 通过对象名访问 | 通过类名访问 |
修改影响 | 仅影响当前对象 | 影响所有实例 |
典型用途 | 存储个体差异的属性 | 存储类级别的常量或计数器 |
八、设计模式示例
-
单例模式
- 确保类只有一个实例,并提供全局访问点。
- 实现方式:
python
运行
class Singleton: _instance = None def __new__(cls): if not cls._instance: cls._instance = super().__new__(cls) return cls._instance
-
工厂模式
- 通过工厂方法创建对象,解耦对象的创建和使用。
- 示例:
python
运行
class AnimalFactory: @staticmethod def create_animal(type): if type == "dog": return Dog() elif type == "cat": return Cat()
九、常见错误与注意事项
-
混淆类属性和实例属性
- 类属性由所有实例共享,修改会影响所有对象。
- 示例:
python
运行
class MyClass: x = [] # 类属性(共享列表) a = MyClass() b = MyClass() a.x.append(1) print(b.x) # 输出: [1](意外共享)
-
多重继承的复杂性
- 遵循 MRO(方法解析顺序)规则,使用
ClassName.mro()
查看顺序。
- 遵循 MRO(方法解析顺序)规则,使用
-
过度使用面向对象
- 简单问题优先使用函数式编程,避免过度设计。
2 为什么要写运算符重载?
在 Python 里,运算符重载能够让你自定义类的对象通过运算符开展运算。
以 __add__()
方法为例,它的作用是重新定义 +
运算符的行为。
虽然 +
运算符原本就具备基础运算行为,不过在处理自定义类的对象时,其默认行为往往无法满足需求。
下面通过几个示例来详细说明这样做的原因:
1. 实现自定义加法逻辑
对于自定义类而言,直接使用 +
运算符会引发错误。这时就需要借助 __add__()
方法来定义特定的加法逻辑。
python
运行
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# 定义向量加法:对应分量相加
return Vector(self.x + other.x, self.y + other.y)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # 等价于 v1.__add__(v2)
print(v3.x, v3.y) # 输出: 4 6
2. 支持混合类型运算
通过运算符重载,自定义类的对象能够和其他类型的对象进行运算。
python
运行
class Money:
def __init__(self, amount, currency):
self.amount = amount
self.currency = currency
def __add__(self, other):
# 支持不同货币相加(简化示例)
if isinstance(other, Money):
if self.currency == other.currency:
return Money(self.amount + other.amount, self.currency)
else:
# 实际应用中可能需要汇率转换
raise ValueError("货币类型不匹配")
elif isinstance(other, int) or isinstance(other, float):
return Money(self.amount + other, self.currency)
else:
return NotImplemented
rmb1 = Money(100, "CNY")
rmb2 = Money(200, "CNY")
total = rmb1 + rmb2 # 同币种相加
print(total.amount) # 输出: 300
3. 构建领域特定语言(DSL)
运算符重载可以让代码的表达更加自然,尤其在数学库的应用中表现突出。
python
运行
import numpy as np
class Matrix:
def __init__(self, data):
self.data = np.array(data)
def __add__(self, other):
# 矩阵加法
return Matrix(self.data + other.data)
def __mul__(self, other):
# 矩阵乘法
return Matrix(self.data @ other.data)
A = Matrix([[1, 2], [3, 4]])
B = Matrix([[5, 6], [7, 8]])
C = A + B # 矩阵加法
D = A * B # 矩阵乘法
总结
- 默认行为局限:Python 原生的
+
运算符只能处理内置类型,像int
、str
等,对于自定义类的对象并不适用。 - 自定义运算规则:借助
__add__()
方法,我们可以让对象支持类似数学的自然运算。 - 提升代码可读性:使用重载后的运算符,能让代码更加简洁明了,例如
a + b
比a.add(b)
更易于理解。
在实际编程中,合理运用运算符重载可以使代码的接口更加直观,就如同内置类型的操作一样自然。
Q:什么叫做直接使用 + 运算符会引发错误?
A:
在 Python 中,如果你直接对自定义类的对象使用 +
运算符而没有定义 __add__()
方法,会引发 TypeError
。
这是因为 Python 不知道如何处理你自定义对象之间的加法运算。
以下是具体的解释和示例:
示例 1:未定义 __add__()
的类
假设你创建了一个简单的 Point
类表示二维平面上的点:
python
运行
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(1, 2)
p2 = Point(3, 4)
result = p1 + p2 # ❌ 直接使用 + 会报错
错误信息:
plaintext
TypeError: unsupported operand type(s) for +: 'Point' and 'Point'
原因:Python 不知道如何将两个 Point
对象相加,因为你没有定义 __add__()
方法来告诉它该怎么做。
示例 2:定义 __add__()
后的类
如果你为 Point
类添加 __add__()
方法,就能定义加法行为:
python
运行
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# 定义点的加法:坐标分别相加
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
result = p1 + p2 # ✅ 现在可以正常使用 + 运算符
print(result.x, result.y) # 输出: 4 6
为什么会报错?
Python 的运算符本质上是方法调用的语法糖:
a + b
等价于a.__add__(b)
a - b
等价于a.__sub__(b)
- 依此类推...
对于内置类型(如 int
、str
、list
),Python 已经内置了这些方法的实现。
但对于自定义类,你需要自己实现这些方法,否则 Python 无法处理对应的运算。
总结
- 直接使用
+
报错的原因:自定义类没有实现__add__()
方法,Python 不知道如何处理该类对象的加法。 - 解决方法:在类中定义
__add__()
方法,明确指定加法的行为。 - 其他运算符:类似地,
-
、*
、/
等运算符也需要通过__sub__()
、__mul__()
、__truediv__()
等方法来重载。
3 Python 的抽象基类(Abstract Base Class, ABC)和 Java 的抽象类(Abstract Class)
Python 的抽象基类(Abstract Base Class, ABC)和 Java 的抽象类(Abstract Class)在概念上有相似之处,但实现方式和使用场景存在差异。以下是详细对比:
核心概念对比
Java 抽象类
- 定义:使用
abstract
关键字声明的类,不能被实例化,只能被继承。 - 抽象方法:用
abstract
声明的方法,只有方法签名,没有实现,子类必须重写。 - 特点:
- 可以包含抽象方法和具体方法。
- 子类必须实现所有抽象方法,否则子类也必须声明为抽象类。
示例:
java
abstract class Shape {
// 抽象方法(没有方法体)
public abstract double area();
// 具体方法
public void printType() {
System.out.println("This is a shape.");
}
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
// 必须实现父类的抽象方法
@Override
public double area() {
return Math.PI * radius * radius;
}
}
Python 抽象基类
- 定义:通过
abc
模块创建,使用@abstractmethod
装饰器声明抽象方法。 - 抽象方法:必须在子类中实现的方法,否则子类无法实例化。
- 特点:
- 可以包含抽象方法和具体方法。
- 使用
register()
或子类化来注册实现类。 - 支持鸭子类型(通过
@abstractmethod
强制实现,而非继承)。
示例:
python
运行
from abc import ABC, abstractmethod
class Shape(ABC):
# 抽象方法
@abstractmethod
def area(self):
pass
# 具体方法
def print_type(self):
print("This is a shape.")
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
# 必须实现基类的抽象方法
def area(self):
return 3.14 * self.radius ** 2
# 无法实例化抽象类
# s = Shape() # TypeError: Can't instantiate abstract class Shape with abstract method area
主要区别
特性 | Java 抽象类 | Python 抽象基类 |
---|---|---|
声明方式 | 使用 abstract 关键字 | 通过继承 ABC 类并使用 @abstractmethod |
实例化限制 | 不能直接实例化 | 不能直接实例化(含未实现的抽象方法时) |
抽象方法强制 | 子类必须实现所有抽象方法 | 子类必须实现所有抽象方法 |
多重继承 | 不支持多重继承(只能继承一个抽象类) | 支持多重继承(可继承多个 ABC) |
注册机制 | 必须通过继承实现 | 可通过 register() 动态注册非子类 |
静态类型检查 | 编译时检查 | 运行时检查 |
具体方法 | 可以包含 | 可以包含 |
Python 抽象基类的额外特性
1. 动态注册实现类
不通过继承,而是通过 register()
方法将类注册为抽象基类的 “虚拟子类”:
python
运行
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square:
def __init__(self, side):
self.side = side
def area(self): # 实现了抽象方法,但未显式继承
return self.side ** 2
# 动态注册
Shape.register(Square)
print(isinstance(Square(5), Shape)) # True
2. 抽象属性
使用 @property
和 @abstractmethod
组合声明抽象属性:
python
运行
from abc import ABC, abstractmethod
class Shape(ABC):
@property
@abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
@property
def area(self): # 实现抽象属性
return 3.14 * self.radius ** 2
总结
-
相似点:
- 都不能直接实例化,需通过子类实现抽象方法。
- 都可以包含抽象方法和具体方法。
-
不同点:
- Python 的 ABC 更灵活,支持动态注册和鸭子类型,而 Java 的抽象类是静态类型系统的一部分。
- Python 通过装饰器声明抽象方法,Java 使用
abstract
关键字。 - Python 的检查发生在运行时,Java 在编译时检查。
两者的核心目的都是定义接口规范,但 Python 的实现更符合其动态语言的特性。
4 if __name__ == "__main__": 这种是什么意思?
if __name__ == "__main__":
是 Python 中一个非常重要的语法结构,用于区分代码是作为独立程序运行还是被其他模块导入。我来详细解释一下:
1. __name__
变量的作用
每个 Python 文件(模块)都有一个内置的特殊变量 __name__
,它的值取决于代码的运行方式:
- 当文件作为独立程序直接运行时:
__name__
的值会被自动设置为"__main__"
。 - 当文件被其他模块导入时:
__name__
的值会被设置为该模块的名称(通常是文件名,不含.py
)。
2. if __name__ == "__main__":
的作用
这个条件语句用于判断当前模块是否是程序的入口点。
如果是,则执行缩进块中的代码;如果不是(即被导入),则跳过。
常见用途:
- 测试代码:在开发模块时,编写一些测试代码,但不希望这些代码在被导入时执行。
- 独立运行:让模块既可以作为库被导入,也可以作为独立程序直接运行。
3. 示例说明
假设你有一个文件 math_utils.py
:
python
运行
# math_utils.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
# 测试函数
def test():
print(f"1 + 2 = {add(1, 2)}")
print(f"5 - 3 = {subtract(5, 3)}")
# 判断是否作为独立程序运行
if __name__ == "__main__":
test()
场景分析:
-
直接运行
math_utils.py
:bash
python math_utils.py
输出:
plaintext
1 + 2 = 3 5 - 3 = 2
此时
__name__ == "__main__"
为True
,测试代码被执行。 -
在其他文件中导入
math_utils
:python
运行
# main.py import math_utils result = math_utils.add(10, 20) print(f"10 + 20 = {result}")
输出:
plaintext
10 + 20 = 30
此时
math_utils.py
中的测试代码不会执行,因为__name__
的值是"math_utils"
,而不是"__main__"
。
4. 为什么需要这种结构?
- 避免副作用:模块中可能包含一些初始化代码或测试代码,不应该在被导入时执行。
- 提高代码复用性:让模块既可以独立运行,也可以被其他程序引用,实现 “一次编写,多种用途”。
5. 在你的员工管理系统中
在之前的代码中,如果添加 if __name__ == "__main__":
,可以将测试代码封装起来:
python
运行
# ... 前面的类定义保持不变 ...
# 测试代码
if __name__ == "__main__":
manager = Manager("张三", "M001", 10000, 0.2)
developer = Developer("李四", "D001", 8000, 3)
print("经理信息:")
manager.display_info()
print("\n开发人员信息:")
developer.display_info()
这样,当这个文件被其他模块导入时,测试代码不会执行,只有作为独立程序运行时才会执行。
总结
if __name__ == "__main__":
是 Python 中一个重要的最佳实践,用于控制代码的执行范围,让模块更加灵活和可复用。建议在编写可导入的模块时始终使用这种结构。