25.Python基础篇-面向对象
一、面向对象编程思想
面向对象编程(OOP) 是一种编程范式,将现实世界的事物建模为对象,通过对象之间的交互实现功能。
OOP 的四大基本特性:
封装(Encapsulation):将数据和操作封装在类中,对外隐藏实现细节。
继承(Inheritance):子类继承父类的属性和方法,实现代码复用。
多态(Polymorphism):不同类的对象可以以统一接口调用,表现出不同行为。
抽象(Abstraction):对对象的复杂部分进行隐藏,仅暴露必要接口。
二、类与对象
定义一个类
class Person: def __init__(self, name, age): # 构造方法 self.name = name # 实例变量 self.age = age def greet(self): # 实例方法 print(f"Hello, my name is {self.name} and I am {self.age} years old.")
创建对象
p1 = Person("Alice", 25) p1.greet() # 输出: Hello, my name is Alice and I am 25 years old.
组合的概念
组合就是一个类的属性,是另一个类的对象(实例)
组合的特点:
- 强调“有一个”关系:对象通过包含其他对象来实现功能,例如“汽车有一个引擎”。
- 松耦合:组合比继承更加灵活,能减少类之间的耦合。
- 优先于继承:通常在子类并不需要完全继承父类行为时,推荐使用组合
演示同一个功能,使用继承和组合两种不同方式的实现:
# 使用继承实现 class Engine: def start(self): print("Engine started") class Car(Engine): # Car 是一个 Engine def drive(self): print("Car is driving") car = Car() car.start() # 输出: Engine started car.drive() # 输出: Car is driving
# 使用组合实现 class Engine: def start(self): print("Engine started") class Car: def __init__(self): self.engine = Engine() # Car 有一个 Engine def drive(self): self.engine.start() print("Car is driving") car = Car() car.drive() # 输出: # Engine started # Car is driving
三、类的基本组成
属性
类属性
属于类本身,是所有对象都能共享的。
可以通过类调用,也可以通过对象调用。按照标准,类属性使用类调用。
class MyClass: class_variable = "类属性" # 创建对象 obj = MyClass() # 通过对象访问类属性 print(obj.class_variable) # 输出: 类属性 # 通过类访问类属性 print(MyClass.class_variable) # 输出: 类属性
注意事项:
通过对象修改类属性时,实际上会在对象的实力属性中创建一个新的同名属性,而不会真正修改类属性。有修改类属性的需求,应使用类名调用进行修改。
class MyClass: class_variable = "类属性" # 创建对象 obj = MyClass() obj.class_variable = "尝试修改类属性" # 在对象上创建了同名实例属性 print(obj.class_variable) # 输出: 尝试修改类属性 print(MyClass.class_variable) # 输出: 类属性(类属性未改变)
实例属性
属于具体对象,各实例独立的
class Person: species = "Homo sapiens" # 类属性 def __init__(self, name, age): self.name = name # 实例属性 self.age = age zs = Person("张三", 18) ls = Person("李四", 19) print(zs.name) # zs.name为zs这个对象的name属性 # 输出:张三 print(ls.name) # ls.name为ls这个对象的name属性。二者相互独立 # 输出:李四 print(zs.species) # zs的species和ls的为同一个值,并且直接用类调也是同样的,因为类属性可以共享 # 输出:Homo sapiens print(ls.species) # 输出:Homo sapiens print(Person.species) # 输出:Homo sapiens
方法
实例方法
第一个参数是 self
,用于操作实例属性
只能使用对象调用
作用
- 表示当前对象:
self
用于访问当前实例的属性和方法。 - 区分局部变量和实例变量:通过
self
明确指定是属于对象的变量,而不是方法中的局部变量。 - 动态绑定属性:允许每个对象拥有自己的独立属性。
class Person: def __init__(self, name, age): self.name = name self.age = age def greet(self): # 实例方法 print("这是实例方法") p = Person("Alice", 25) p.greet() # 通过对象调用实例方法 # 输出:这是实例方法
类方法
1.使用 @classmethod
装饰,第一个参数是 cls
,操作类属性。
2.cls表示当前类。
2.1cls的作用:
cls
可以访问类的方法和属性
在继承关系中,使用cls可以动态指向子类,确保在类方法中调用的是子类的行为。
3.可以通过对象调用,也可以通过类调用。但是按照规范应使用类调用。
class MyClass: class_variable = "I am a class variable" @classmethod def class_method(cls): return f"Accessing: {cls.class_variable}" # 调用 print(MyClass.class_method()) # 输出: Accessing: I am a class variable obj = MyClass() print(obj.class_method()) # 输出: Accessing: I am a class variable
4.典型应用场景
class Counter: count = 0 # 类属性 @classmethod def increment(cls): cls.count += 1 # 调用 Counter.increment() Counter.increment() print(Counter.count) # 输出: 2
静态方法
1.使用 @staticmethod
装饰,不需要 self
或 cls
参数。
2.具有特征:既不操作实例属性,也不操作类属性。
3.在完全的面向对象编程语言中用的多,因为每个方法都必须写在类中。Python因为也可以面向函数编程,所以并不是一定需要。
4.应用场景:
当前静态方法与当前类并没有什么引用关系,只是放在这里个类中而已。
比如:工具类。一个工具类中有很多通用的方法,这些方法之间没有关联关系,且并不引用类中的内容。
class Calculator: @staticmethod def add(a, b): # 静态方法 return a + b print(Calculator.add(5, 10)) # 输出: 15
三种方法的对比总结:
适用于不同的场景
实例方法
- 定义实例行为,如获取或修改实例属性。
类方法
- 创建工厂方法(如通过不同参数构造类的实例)。
- 处理全局状态共享。
静态方法
- 定义工具函数,逻辑与类有关但不需要类的上下文(如数学运算)。
四、内置方法
__init__():初始化方法,也叫类的构造方法
每次创建类的对象时,__init__()
都会自动调用。不是必须要写的。init方法需要的参数,就是实例化一个类的时候需要传的参数
class Person: def __init__(self, name, age): # 第一个参数必须是self,表示当前实例(对象)。其他参数可以自定义 self.name = name # 将参数值赋给实例属性 self.age = age # 创建对象时会调用 __init__() p1 = Person("Alice", 25) p2 = Person("Bob", 30) print(p1.name, p1.age) # 输出: Alice 25 print(p2.name, p2.age) # 输出: Bob 30
与普通方法的区别:
- 调用时机:
__init__()
在创建对象时自动调用,普通方法需要通过实例调用。 - 用途:
__init__()
通常用于初始化实例属性,而普通方法用于实现实例的其他功能。
注意事项:
__init__()
不是构造对象的方法:实际创建对象的过程是由__new__()
方法完成的,__init__()
只是用于初始化对象。- 不要返回值:
__init__()
不能返回值,否则会引发错误。
__repr__
使用 repr()
或者在解释器中输入对象时,__repr__
方法会被自动调用。
class Dog: def __init__(self, name, age): self.name = name self.age = age def __repr__(self): return f"Dog('{self.name}', {self.age})" dog = Dog('Buddy', 3) #使用repr方法时会自动调用__repr__方法 print(repr(dog)) # 输出:Dog('Buddy', 3)
__str__
当print()或者str()打印对象时,__str__
方法会被调用。
class Dog: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"{self.name} is {self.age} years old." dog = Dog('Buddy', 3) print(str(dog)) # 输出:Buddy is 3 years old.
__str__与__repr__两个方法的关系
当使用print或str方法打印一个对象时,会优先调用__str__,如果当前对象没有实现__str__,则会调用__repr__。
但是当使用repr()时,对象没有实现__repr__会调用父类的__repr__,并不会再调用__str__。
class A: def __repr__(self): return "__repr__" a = A() print(a) # __repr__:没有str会调用__repr__ print(str(a)) # __repr__ print(repr(a)) # __repr__ class A: def __str__(self): return "__str__" a = A() print(repr(a)) # <__main__.A object at 0x1047a9310>
__len__
调用内建函数 len()
时自动调用的方法
class Dog: def __init__(self, name, age): self.name = name self.age = age def __len__(self): return len(self.name) dog = Dog('Buddy', 3) print(len(dog)) # 输出:5,返回名字的长度
__new__
__new__
是一个创建对象的特殊方法,它在__init__
方法之前调用。__new__
负责创建对象的实例,而__init__
则负责初始化对象。__new__
通常在涉及单例模式或自定义对象创建行为时使用。
# __new__方法的应用场景:单例 # __new__方法确保只有一个Singleton实例被创建,实现了单例模式。 class Singleton: _instance = None def __new__(cls): if not cls._instance: cls._instance = super(Singleton, cls).__new__(cls) return cls._instance s1 = Singleton() s2 = Singleton() print(s1 is s2) # 输出:True,两个对象是同一个实例
__del__
析构函数,当对象被垃圾回收时自动调用。通常用于释放对象占用的外部资源,如文件句柄、网络连接等。
当使用del关键字删除时,会先自动调用对象的__del__方法,再从内存中删除对象。
class Dog: def __init__(self, name): self.name = name print(f"{self.name} is created!") def __del__(self): print(f"{self.name} is destroyed!") dog = Dog('Buddy') del dog # 输出:Buddy is destroyed!
__call__
得对象变得可调用。换句话说,如果我们定义了__call__
,我们可以像调用函数一样调用该对象。
class Dog: def __init__(self, name): self.name = name def __call__(self): print(f"{self.name} is called!") dog = Dog("Buddy") # 在对象后面加上括号,像调用函数一样写时,会自动执行该对象的__call__方法 dog() # 输出:Buddy is called!
tem系列
__getitem__
允许通过方括号 []
的形式获取对象的指定项,类似于访问列表或字典的元素。
__setitem__
允许通过方括号 []
的形式为对象的指定项赋值。
__delitem__
允许通过方括号 []
的形式删除对象的指定项。
代码演示
class Foo: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def __getitem__(self, item): if hasattr(self,item): return self.__dict__[item] def __setitem__(self, key, value): self.__dict__[key] = value def __delitem__(self, key): del self.__dict__[key] f = Foo("zs", 30, "男") # 使用[]调用getitem,以访问对象的属性 print(f['name']) # 输出:zs # 使用[]调用setitem,给对象添加一个属性 f['hobby'] = "女" print(f.hobby) # 输出:男 print(f.__dict__) # 输出:{'name': 'zs', 'age': 30, 'sex': '男', 'hobby': '男'} # 调用delitem方法 del f["hobby"] print(f.__dict__) # 输出:{'name': 'zs', 'age': 30, 'sex': '男'}
__eq__
在使用 ==
比较两个对象时的行为。也就是说当使用==时会自动调用左边对象的__eq__方法。根据__eq__方法实现的自定义相等性来判断两个对象是否相等
代码演示:
class Person: def __init__(self, name, age): self.name = name self.age = age def __eq__(self, other): if isinstance(other, Person): return self.name == other.name and self.age == other.age return False p1 = Person('Alice', 30) p2 = Person('Alice', 30) p3 = Person('Bob', 25) print(p1 == p2) # 输出:True print(p1 == p3) # 输出:False
__hash__
返回对象的哈希值
如果实现了__eq__
,且对象需要用于哈希集合(如set
或字典的键),通常也需要实现__hash__
方法
代码演示:
class Person: def __init__(self, name, age): self.name = name self.age = age def __eq__(self, other): if isinstance(other, Person): return self.name == other.name and self.age == other.age return False def __hash__(self): return hash((self.name, self.age)) p1 = Person('Alice', 30) p2 = Person('Alice', 30) p3 = Person('Bob', 25) people = {p1, p2, p3} print(len(people)) # 输出:2,因为p1和p2被认为是相等的
五、继承
继承的基本概念
- 继承:子类继承父类的属性和方法,代码可复用。
- 父类(基类):被继承的类。
- 子类(派生类):继承父类的类,可以添加或重写父类的方法。
Python中的继承介绍:
1.Python3中,所有的类都是新式类。只有在新式类中才可以使用super关键字。
2.并且Python3中所有的类都继承自object类。
3.在继承中如果子类和父类共同拥有一个属性或者方法,则优先使用子类。子类没有则用父类。
单继承
super关键字:用于调用父类的方法,是继承中非常常用的关键字。
class Parent: def __init__(self, name): self.name = name class Child(Parent): def __init__(self, name, age): super().__init__(name) # 调用父类构造方法 self.age = age child = Child("Alice", 10) print(child.name) # 输出:Alice print(child.age) # 输出:10
单继承代码演示
# 定义父类 class Animal: def __init__(self, name): self.name = name def speak(self): return f"{self.name} says Woof!" # 定义子类 class Dog(Animal): # 括号内为要继承的父类 pass class Cat(Animal): # 括号内为要继承的父类 pass # 测试 dog = Dog("Buddy") cat = Cat("Kitty") print(dog.speak()) # 输出:Buddy says Woof! print(cat.speak()) # 输出:Kitty says Meow!
方法的重写(Override)
子类可以重新定义父类的方法,覆盖父类的方法实现。
class Parent: def greet(self): return "Hello from Parent" class Child(Parent): def greet(self): return "Hello from Child" child = Child() print(child.greet()) # 输出:Hello from Child
多继承
多继承说明
多继承是指一个类继承多个父类
C.__mro__
查看类的继承顺序(方法解析顺序)。
代码演示:
class A: def greet(self): return "Hello from A" class B: def greet(self): return "Hello from B" class C(A, B): # 多继承 pass c = C() print(c.greet()) # 输出:Hello from A (根据继承顺序) print(C.__mro__) # 输出:(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>) # 继承顺序为,优先继承C,C没有再去A找。最后直到object类
多继承中的继承顺序
广度优先规则 python3和python2中的新式类使用
深度优先规则 python2中的经典类使用
多继承中使用super关键字显式调用指定父类:
class D(B, C): # D继承自B和C def __init__(self): super(B, self).__init__() # 显式调用B的__init__方法 print("D's __init__") def speak(self): super(C, self).speak() # 显式调用C的speak方法 # 测试 d = D() d.speak() # 显式调用C的speak
六、接口类与抽象类
接口类
介绍:接口类用于定义一组方法签名(即方法的声明),这些方法没有实现。接口类强调“行为”而不是“实现”,用于为不同的类提供统一的接口规范。
Python中的接口类:
Python没有内置的interface
关键字,但可以通过**抽象基类(ABC)**来模拟接口的行为。所有方法都使用@abstractmethod
标记,并且不能有方法实现。
用法代码演示:
from abc import ABC, abstractmethod class Animal(ABC): # 定义一个接口类 @abstractmethod def speak(self): pass class Dog(Animal): def speak(self): return "Woof!" class Cat(Animal): pass # 不能在Python中定义一个真正的接口类,但可以通过这种方式来模拟接口行为 dog = Dog() print(dog.speak()) # 输出:Woof! # Cat类中没有实现speak方法,运行是会报错 cat = Cat() print(cat.speak()) # 报错:TypeError: Can't instantiate abstract class Cat with abstract method speak
特征:
没有实现:接口类的所有方法没有实现,只定义方法签名。
提供统一的行为规范:所有实现接口的类必须实现接口中的方法。
灵活性高:多个类可以实现同一个接口,不同类有不同的实现方式。
抽象类
介绍:抽象类是不能实例化的类,它提供了对子类的行为约定。抽象类可以包含已实现的方法(带有实现的函数)和抽象方法(仅定义,没有实现)。抽象类用于定义一些公共的行为,让子类继承并具体实现。
Python中的抽象类:
在Python中,抽象类通过abc
模块来实现,使用ABC
类来定义抽象类,并通过@abstractmethod
装饰器定义抽象方法。
用法代码演示:
from abc import ABC, abstractmethod class Animal(ABC): # 定义一个抽象类Animal,继承ABC def __init__(self, name): self.name = name @abstractmethod # 抽象方法,子类必须实现 def speak(self): pass class Dog(Animal): def speak(self): return f"{self.name} says Woof!" # animal = Animal("Generic") # 错误:不能实例化抽象类 dog = Dog("Buddy") print(dog.speak()) # 输出:Buddy says Woof!
特征:
可以包含具体实现:例如Animal
类中没有实现的speak
方法是抽象方法,而构造函数是已实现的方法。
子类必须实现抽象方法:Dog
类实现了抽象方法speak
。
不能实例化:你不能直接实例化抽象类,必须通过继承来创建子类实例。
接口类与抽象类的区别
七、多态
多态就是“一个接口,多种实现”。在Python中,多态的实现主要依赖于方法的重写和鸭子类型。
Python天生是支持多态的。
多态的代码演示:
class Animal: def speak(self): raise NotImplementedError("Subclasses should implement this!") class Dog(Animal): def speak(self): return "Woof!" class Cat(Animal): def speak(self): return "Meow!" # 多态示例 animals = [Dog(), Cat()] for animal in animals: print(animal.speak()) #输出: #Woof! #Meow!
鸭子类型
鸭子类型强调的是对象的行为而不是其类型。只要一个对象实现了所需的方法或属性,就可以被认为是一个合适的类型。
换句话说,Python不关心一个对象是哪个类的实例,只要这个对象拥有你想要调用的接口(方法或属性),就可以使用它
class Dog: def speak(self): print("Woof!") class Cat: def speak(self): print("Meow!") class Duck: def speak(self): print("Quack!") def make_sound(animal): animal.speak() # 不关心animal的具体类型,只关心它是否有speak方法 # 测试 make_sound(Dog()) # 输出:Woof! make_sound(Cat()) # 输出:Meow! make_sound(Duck()) # 输出:Quack!
在上面的代码中,make_sound
函数并不关心传入的对象是Dog
、Cat
,还是Duck
,只要它们有一个speak
方法,就可以调用。这就是鸭子类型的体现:只要对象具有某些特定的方法或行为,就可以当作所需的类型使用。
鸭子类型的优势:
灵活性:你不需要显式检查对象的类型,只要它符合行为规范,就可以使用它。这使得Python的代码更加简洁和灵活。
解耦:鸭子类型避免了对对象类型的强依赖,使得代码可以更加松耦合。这有助于提高代码的可扩展性。
适应性强:无论一个类是如何定义的,只要实现了所需的方法,它就能在某个上下文中使用。比如,你可以随时让一个新类实现duck
类型的方法,而不需要修改其他已有代码。
八、封装
封装的概念介绍
将对象的状态(属性)和行为(方法)包装在一起,对外提供访问接口,同时隐藏内部实现细节。封装通过访问控制的方式,保护数据不被直接修改,确保数据的安全性和一致性。
封装的目的
隐藏实现细节:将复杂的实现细节隐藏在对象内部,只暴露简洁的接口。
提高代码的安全性:通过限制外部对数据的访问和修改,防止不当的操作。
提高可维护性:更容易更改和扩展内部实现,而不影响外部代码。
私有属性
受保护属性:使用一个下划线标记,外部不应该访问,但是Python没有强制禁止。
私有属性:属性名前面使用两个下划线“__”标记,标记之后外部不能直接访问
私有方法
受保护方法:使用一个下划线标记,外部不应该访问,但是Python没有强制禁止。
方法名前面使用两个下划线“__”标记,标记之后外部不能直接访问
class Car: def __init__(self, make, model): self.make = make # 公共属性 self._model = model # 受保护属性 self.__year = 2020 # 私有属性 def get_year(self): return self.__year # 提供对私有属性的访问接口 def set_year(self, year): if year > 1885: # 检查年份是否合法 self.__year = year else: print("Invalid year!") car = Car("Toyota", "Corolla") print(car.make) # 输出:Toyota (公开属性) print(car._model) # 输出:Corolla (受保护属性,虽然可以访问,但不推荐) # print(car.__year) # 会抛出错误:AttributeError: 'Car' object has no attribute '__year' # 正确访问私有属性 print(car.get_year()) # 输出:2020 car.set_year(2025) # 设置新的年份 print(car.get_year()) # 输出:2025
@property装饰器
介绍:可以将一个方法转换为属性,提供了控制属性访问的能力,同时允许通过方法来动态计算属性值。
代码演示:
class Circle: def __init__(self, radius): self._radius = radius @property # 通过 @property 提供了只读访问,外部只能读取radius但不能直接修改。 def radius(self): return self._radius @radius.setter # radius.setter 提供了对 radius 属性的写访问,通过实现这个方法,可以实现修改radius def radius(self, value): if value > 0: self._radius = value else: print("Radius must be positive!") @property def area(self): return 3.14 * self._radius ** 2 circle = Circle(5) print(circle.radius) # 5 print(circle.area) # 78.5 circle.radius = 10 # 设置新的半径 print(circle.area) # 314.0 circle.radius = -3 # 错误的半径值
九、反射
反射的概念:反射可以在运行时获取对象的属性和方法;可以动态调用方法或访问属性;动态修改对象的属性值。
Python中的反射:Python没有显式的反射机制,但它提供了通过内置函数和方法来实现类似反射的功能,常见的包括:
getattr()
setattr()
hasattr()
delattr()
getattr()
函数用于获取对象的属性。如果该属性不存在,可以指定一个默认值。
getattr(object, attribute_name[, default])
object
:要获取属性的对象。
attribute_name
:属性的名称,可以是字符串。
通过字符串获取变量:
class Dog: def __init__(self, name): self.name = name dog = Dog("Buddy") # 获取name属性 print(getattr(dog, "name")) # 输出:Buddy # 如果属性不存在,返回默认值 print(getattr(dog, "age", 3)) # 输出:3
setattr()
用于设置对象的属性。如果该属性不存在,会创建新属性
setattr(object, attribute_name, value)
object
:要设置属性的对象。attribute_name
:属性名称。value
:设置的值。
class Dog: def __init__(self, name): self.name = name dog = Dog("Buddy") # 修改name属性 setattr(dog, "name", "Charlie") print(dog.name) # 输出:Charlie # 动态设置新的属性 setattr(dog, "age", 5) print(dog.age) # 输出:5
hasattr()
用于检查对象是否具有指定的属性。
hasattr(object, attribute_name)
object
:要检查的对象。attribute_name
:要检查的属性名称。
class Dog: def __init__(self, name): self.name = name dog = Dog("Buddy") # 检查是否具有name属性 print(hasattr(dog, "name")) # 输出:True # 检查是否具有age属性 print(hasattr(dog, "age")) # 输出:False
可以在getattr方法使用前,先用hasattr检查要获取的变量是否存在。
delattr()
用于删除对象的属性。
delattr(object, attribute_name)
object
:要删除属性的对象。attribute_name
:要删除的属性名称。
class Dog: def __init__(self, name): self.name = name dog = Dog("Buddy") # 删除name属性 delattr(dog, "name") print(hasattr(dog, "name")) # 输出:False
除了动态操作属性之外,还可以动态调用方法
class Dog: def __init__(self, name): self.name = name def speak(self): print(f"{self.name} says woof!") dog = Dog("Buddy") # 动态获取并调用speak方法 method = getattr(dog, "speak") method() # 输出:Buddy says woof!
反射的应用场景
- 动态方法调用:可以根据字符串动态调用类中的方法,常见于插件系统、框架设计等。
- 对象序列化与反序列化:可以根据对象的属性动态地进行序列化或反序列化。
- API调用:在API开发中,可以根据请求动态调用相应的方法。
- 测试与调试:可以通过反射检查对象的状态、方法或属性,方便进行单元测试和调试。
反射的优缺点
优点:
- 灵活性:可以动态地访问和操作对象的属性、方法,增强了程序的灵活性。
- 简化代码:能够通过少量代码实现复杂的动态功能,避免手动编写大量的条件判断和硬编码。
- 适用于插件和框架开发:反射在插件化架构、框架开发等场景中非常有用,可以根据需求动态加载和调用功能模块。
缺点:
- 降低代码可读性:反射通过动态操作对象的属性和方法,可能导致代码不易理解,增加了代码的复杂性。
- 运行时错误:由于反射是基于字符串操作的,运行时可能出现属性或方法不存在的错误,增加了出错的风险。
- 性能开销:反射会增加一定的性能开销,尤其是在高频调用的情况下。
反射总结
- 反射是Python的一项强大特性,可以在运行时动态地访问、修改对象的属性和方法。
- Python的反射通过
getattr()
、setattr()
、hasattr()
和delattr()
等函数实现,允许在代码执行过程中动态获取和操作对象的行为。 - 反射使得程序更加灵活、可扩展,但也可能导致代码的可读性下降,增加出错的可能性。
- 在实际应用中,反射常用于框架设计、插件系统、动态API调用等场景。