学习日志08 python
嘎嘎嘎嘎嘎今天知道了黑客松,实在是太燃了,这比挑战杯什么老牌的比赛更重视技术,非常燃啊!!!!
好了,白天不知道在摸什么鱼,冲啊,好想看科技会展啊,感觉是很新的科技展会,太有活力了!(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 # 类属性@classmethoddef add_car(cls):cls.total_cars += 1 # 类方法
静态方法
- 不依赖类或实例的工具方法,使用
@staticmethod
装饰器。 - 示例:
python
运行
class Car:@staticmethoddef convert_mph_to_kph(mph):return mph * 1.609 # 静态方法
- 不依赖类或实例的工具方法,使用
三、继承与多态
单继承
- 子类继承父类的属性和方法。
- 语法:
class Child(Parent):
- 示例:
python
运行
class Vehicle:def move(self):print("Moving...")class Car(Vehicle): # 继承 Vehiclepass
方法重写
- 子类覆盖父类的方法。
- 示例:
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@propertydef radius(self):return self.__radius@radius.setterdef radius(self, value):if value > 0:self.__radius = value
- 使用
五、特殊方法(魔术方法)
对象表示
__str__()
:返回对象的用户友好字符串表示(str()
调用)。__repr__()
:返回对象的开发者友好字符串表示(调试时使用)。- 示例:
python
运行
class Point:def __init__(self, x, y):self.x = xself.y = ydef __str__(self):return f"({self.x}, {self.y})"
运算符重载
__add__()
:定义+
运算符行为。__len__()
:定义len()
函数行为。- 示例:
python
运行
class Vector:def __init__(self, x, y):self.x = xself.y = ydef __add__(self, other):return Vector(self.x + other.x, self.y + other.y)
对象比较
__eq__()
:定义==
运算符行为。__lt__()
:定义<
运算符行为。- 示例:
python
运行
class Person:def __init__(self, age):self.age = agedef __eq__(self, other):return self.age == other.age
六、高级特性
抽象基类(ABC)
- 定义接口,强制子类实现特定方法。
- 使用
abc
模块的@abstractmethod
装饰器。 - 示例:
python
运行
from abc import ABC, abstractmethodclass Shape(ABC):@abstractmethoddef area(self):pass
Mixin 类
- 提供特定功能的小型类,用于多重继承。
- 示例:
python
运行
class LoggableMixin:def log(self):print(f"Logging: {self.__class__.__name__}")
类的动态特性
- 动态添加属性和方法:
python
运行
class MyClass:passobj = MyClass() obj.new_attr = 42 # 动态添加属性
- 动态添加属性和方法:
七、实例与类的区别
特性 | 实例 | 类 |
---|---|---|
数据存储 | 存储对象特定的数据 | 存储所有实例共享的数据 |
访问方式 | 通过对象名访问 | 通过类名访问 |
修改影响 | 仅影响当前对象 | 影响所有实例 |
典型用途 | 存储个体差异的属性 | 存储类级别的常量或计数器 |
八、设计模式示例
单例模式
- 确保类只有一个实例,并提供全局访问点。
- 实现方式:
python
运行
class Singleton:_instance = Nonedef __new__(cls):if not cls._instance:cls._instance = super().__new__(cls)return cls._instance
工厂模式
- 通过工厂方法创建对象,解耦对象的创建和使用。
- 示例:
python
运行
class AnimalFactory:@staticmethoddef 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 = xself.y = ydef __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 = amountself.currency = currencydef __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 NotImplementedrmb1 = Money(100, "CNY")
rmb2 = Money(200, "CNY")
total = rmb1 + rmb2 # 同币种相加
print(total.amount) # 输出: 300
3. 构建领域特定语言(DSL)
运算符重载可以让代码的表达更加自然,尤其在数学库的应用中表现突出。
python
运行
import numpy as npclass 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 = xself.y = yp1 = 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 = xself.y = ydef __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;}// 必须实现父类的抽象方法@Overridepublic double area() {return Math.PI * radius * radius;}
}
Python 抽象基类
- 定义:通过
abc
模块创建,使用@abstractmethod
装饰器声明抽象方法。 - 抽象方法:必须在子类中实现的方法,否则子类无法实例化。
- 特点:
- 可以包含抽象方法和具体方法。
- 使用
register()
或子类化来注册实现类。 - 支持鸭子类型(通过
@abstractmethod
强制实现,而非继承)。
示例:
python
运行
from abc import ABC, abstractmethodclass Shape(ABC):# 抽象方法@abstractmethoddef 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, abstractmethodclass Shape(ABC):@abstractmethoddef area(self):passclass Square:def __init__(self, side):self.side = sidedef area(self): # 实现了抽象方法,但未显式继承return self.side ** 2# 动态注册
Shape.register(Square)print(isinstance(Square(5), Shape)) # True
2. 抽象属性
使用 @property
和 @abstractmethod
组合声明抽象属性:
python
运行
from abc import ABC, abstractmethodclass Shape(ABC):@property@abstractmethoddef area(self):passclass Circle(Shape):def __init__(self, radius):self.radius = radius@propertydef 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 + bdef 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_utilsresult = 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 中一个重要的最佳实践,用于控制代码的执行范围,让模块更加灵活和可复用。建议在编写可导入的模块时始终使用这种结构。