7. 面向对象编程
1. 类和对象
1. 类的定义
class 类名:
实行定义
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
Python约定类名首字母大写,这是为了和函数名区分开。
参数列表里的 self 是一个约定俗成的参数名,它代表类的实例对象本身。
self 的作用
- 引用实例属性:在类的方法内部,可以使用
self来引用实例对象的属性。 - 调用实例方法:通过
self可以在一个实例方法中调用该实例的其他方法。 - 区分实例属性和局部变量:使用
self可以明确表示访问的是实例对象的属性,而不是方法内部的局部变量。
2. 创建对象
创建:
对象变量 = 类名()
访问:
对象名.成员
在Python中引用和其他面向对象编程语言中的引用意思相同,表示的是一个内存地址。当类创建出一个对象之后,对象变量记录的是对象在内存中的地址。
3. self参数
Python规定self必须是方法的第一个参数。
self类似于 java 中的this。由哪个对象调用的方法,方法内的self表示的就是哪个对象的引用。在封装的方法内部,self就表示当前调用方法的对象自身,调用方法时,不需要传递参数给self。在方法内部,可以通过self访问对象的属性,也可以通过”self."的形式调用其他的对象方法。
1. self 表示调用方法的对象引用
在 Python 类中,实例方法的第一个参数通常命名为 self,它代表的是调用该方法的对象本身。也就是说,当你使用某个对象去调用类中的方法时,Python 会自动将这个对象作为第一个参数传递给方法,这个参数就是 self。
2. 调用方法时无需传递参数给 self
当你调用一个实例方法时,不需要手动传递参数给 self,Python 会自动处理这个过程。
3. 在方法内部通过 self 访问对象属性和调用其他方法
在实例方法内部,可以使用 self 来访问对象的属性,也可以通过 self. 的形式调用对象的其他方法。
示例代码
class Person:
def __init__(self, name, age):
# 初始化对象的属性
self.name = name
self.age = age
def introduce(self):
# 通过 self 访问对象的属性
print(f"我叫 {self.name},今年 {self.age} 岁。")
def have_birthday(self):
# 通过 self 访问对象的属性并修改
self.age += 1
print(f"祝 {self.name} 生日快乐!现在 {self.name} {self.age} 岁了。")
# 通过 self 调用对象的其他方法
self.introduce()
# 创建 Person 类的对象
p1 = Person("张三", 20)
# 调用 introduce 方法
p1.introduce()
# 调用 have_birthday 方法
p1.have_birthday()
代码解释
__init__方法:这是一个特殊的实例方法,用于初始化对象的属性。self代表当前创建的对象,self.name和self.age分别是对象的属性。introduce方法:在这个方法内部,通过self.name和self.age访问对象的属性,并将其打印出来。have_birthday方法:- 通过
self.age += 1修改对象的age属性。 - 打印生日快乐的信息。
- 通过
self.introduce()调用对象的introduce方法。 - 创建对象并调用方法:创建了一个
Person类的对象p1,并调用了introduce和have_birthday方法。在调用方法时,不需要手动传递参数给self。
4. 初始化方法
初始化方法就是_ _init_ _()方法,它是对象的内置方法,专门用来定义一个类具有哪些属性。与java中的构造方法类似。
_ _init_ _()方法的第一个参数必须是self,代表当前对象的引用。
在程序中并不一定需要显式的定义_ _init_ _()方法,系统会提供一个默认的_ _init_ _()方法来执行。
5. 析构方法
Python 中的析构方法名为 __del__(),它是类的一个特殊方法。当对象的引用计数为 0 或者程序结束时,Python 的垃圾回收机制会自动调用该对象的 __del__() 方法。其基本语法如下:
class ClassName:
def __del__(self):
# 在这里编写对象销毁时需要执行的代码
pass
析构方法主要用于在对象被销毁前执行一些清理工作,例如关闭文件、释放网络连接、释放数据库连接等资源,以避免资源泄漏。
2. 类成员和实例成员
1. 实例属性
实例属性是属于对象的属性,每个对象都有自己独立的实例属性值。可以在 __init__ 方法中初始化实例属性。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person1 = Person("Alice", 25)
print(person1.name) # 输出: Alice
print(person1.age) # 输出: 25
__init__ 是一个特殊的方法,也称为构造方法,在创建对象时会自动调用,用于初始化对象的属性。self 是一个约定俗成的参数名,它代表类的实例本身。
2. 实例方法
实例方法是属于对象的方法,只能通过对象来调用。实例方法的第一个参数通常是 self,用于访问对象的属性和方法。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"My name is {self.name} and I am {self.age} years old.")
person1 = Person("Alice", 25)
person1.introduce() # 输出: My name is Alice and I am 25 years old.
3. 类属性
类属性是属于类的属性,所有对象共享同一个类属性值。类属性可以在类的内部定义,不需要 self 关键字。
class Person:
species = "Homo sapiens"
def __init__(self, name, age):
self.name = name
self.age = age
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
print(person1.species) # 输出: Homo sapiens
print(person2.species) # 输出: Homo sapiens
4. 类方法
类方法是属于类的方法,通过 @classmethod 装饰器来定义,第一个参数通常是 cls,代表类本身。类方法可以通过类名或对象名来调用。
class Person:
species = "Homo sapiens"
@classmethod
def get_species(cls):
return cls.species
print(Person.get_species()) # 输出: Homo sapiens
person1 = Person("Alice", 25)
print(person1.get_species()) # 输出: Homo sapiens
5. 静态方法
静态方法是一种独立于类和对象的方法,通过 @staticmethod 装饰器来定义,不需要 self 或 cls 参数。静态方法可以通过类名或对象名来调用。
class Person:
@staticmethod
def is_adult(age):
return age >= 18
print(Person.is_adult(25)) # 输出: True
person1 = Person()
print(person1.is_adult(15)) # 输出: False
3. 私有属性和私有方法
1. 介绍
封装是指将数据和操作数据的方法隐藏在类的内部,通过公共的接口来访问和修改数据。在 Python 中,没有严格的访问控制,但可以通过约定来实现封装,例如使用单下划线 _ 表示受保护的属性和方法,使用双下划线 __ 表示私有的属性和方法。
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"Deposited {amount}. New balance: {self.__balance}")
else:
print("Invalid deposit amount.")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"Withdrew {amount}. New balance: {self.__balance}")
else:
print("Invalid withdrawal amount.")
account = BankAccount(1000)
account.deposit(500) # 输出: Deposited 500. New balance: 1500
account.withdraw(200) # 输出: Withdrew 200. New balance: 1300
在上面的例子中,__balance 是一个私有属性,外部无法直接访问,只能通过 deposit 和 withdraw 方法来操作。
私有的属性和方法(双下划线 __)
2. 定义
以双下划线 __ 开头的属性和方法被视为私有的成员。Python 会对这类成员进行名称修饰(Name Mangling),使得在类的外部不能直接通过原来的名称访问。
3. 示例代码
class MyClass:
def __init__(self):
self.__private_attribute = 42
def __private_method(self):
print("This is a private method.")
def public_method(self):
# 在类内部可以访问私有属性和方法
print(self.__private_attribute)
self.__private_method()
# 创建对象
obj = MyClass()
# 尝试在类外部直接访问私有属性,会报错
# print(obj.__private_attribute)
# 尝试在类外部直接调用私有方法,会报错
# obj.__private_method()
# 通过公共方法间接访问私有属性和方法
obj.public_method()
4. 解释
在上述代码中,__private_attribute 是私有属性,__private_method 是私有方法。当尝试在类的外部直接访问它们时,会引发 AttributeError 异常。但在类的内部,如 public_method 中,可以正常访问和调用这些私有成员。
但使用对象名._类名__属性名仍然可以访问属性值。不是严格意义上的强制访问限制机制。
Python 在处理以双下划线开头的属性和方法时,会对其名称进行改写(名称修饰),以避免子类意外覆盖父类的私有成员。
Python 对以双下划线开头但不以双下划线结尾的属性和方法名进行名称修饰时,会在原名称前加上一个下划线和类名。具体来说,如果类名为 ClassName,属性或方法名为 __attribute_name,那么经过名称修饰后的名称为 _ClassName__attribute_name。
5. (补)保护的
受保护的属性和方法(单下划线 _)
定义
在 Python 里,以单下划线 _ 开头的属性和方法被视为受保护的成员。这只是一种约定,意味着这些成员不应该在类的外部直接访问,但实际上并没有强制限制。
示例代码
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
def _display_age(self):
print(f"The age is {self._age}.")
# 创建对象
person = Person("Alice", 25)
# 可以在类外部访问受保护的属性
print(person._name)
# 可以在类外部调用受保护的方法
person._display_age()
解释
从上述代码可以看出,虽然 _name 和 _age 是受保护的属性,_display_age 是受保护的方法,但 Python 并不会阻止在类的外部直接访问和调用它们。这种单下划线的命名方式更多是一种开发者之间的约定,提示这些成员是类内部使用或不建议外部直接访问的。
4. 继承
继承是面向对象编程的一个重要特性,它允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以扩展或重写父类的方法。
1. 单一继承
单一继承指的是一个子类只继承自一个父类。
格式:
class 类名 (父类名):
pass
1. 方法重写
子类可以重写父类的方法,以改变其行为。以下是一个方法重写的示例:
class Animal:
def speak(self):
print("动物发出声音")
class Cat(Animal):
def speak(self):
print("猫咪喵喵叫")
cat = Cat()
cat.speak()
代码解释:
Animal类有一个speak方法。Cat类继承自Animal类,并重写了speak方法。- 创建
Cat类的实例cat后,调用speak方法时,会执行子类重写后的方法。
2. 调用父类的方法
在子类中,可以使用 super() 函数来调用父类的方法。以下是一个示例:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} 发出声音")
class Bird(Animal):
def __init__(self, name, wingspan):
# 调用父类的构造方法
super().__init__(name)
self.wingspan = wingspan
def speak(self):
# 调用父类的 speak 方法
super().speak()
print(f"{self.name} 叽叽喳喳叫,翅膀展开 {self.wingspan} 厘米")
bird = Bird("啾啾", 20)
bird.speak()
代码解释:
Bird类的构造方法中使用super().__init__(name)调用父类的构造方法,以初始化name属性。Bird类的speak方法中使用super().speak()调用父类的speak方法,然后再添加自己的行为。
3. 父类的私有属性和方法
子类对象不能再自己的方法内部直接访问父类的私有属性和方法,但是子类对象可以通过父类的公有方法间接访问问父类的私有属性或方法。
2. 多重继承
Python支持多重继承,子类可以拥有多个父类,并且所有父类的属性和方法,多重继承的语法格式如下:
class 子类名(父类名1.父类名2....):
pass
示例:
class Parent1:
def method1(self):
print("Parent1 method1")
class Parent2:
def method2(self):
print("Parent2 method2")
class Child(Parent1, Parent2):
pass
child = Child()
child.method1()
child.method2()
当一个类从多个父类继承时,可能会出现方法名冲突的情况。Python 提供了一种内置属性__mro__,使用一种特定的算法来确定方法的解析顺序,即从左到右深度优先搜索。可以通过ClassName.__mro__属性来查看类的方法解析顺序。例如,在上面的例子中,Child.__mro__会显示(Child, Parent1, Parent2, object),这表示在查找方法时,首先会在Child类中查找,如果找不到,会按照顺序在Parent1、Parent2和object类中查找。
多重继承其实很容易导致代码混乱,所以当不确定是否真的必须使用多重继承的时候,请尽量避免。
5. 多态
多态是面向对象的特征之一,它以继承和重写父类方法为前提,当不同子类对象调用相同的父类方法,而产生不同的执行结果,这就称之为多态。
在 Python 中,可以通过定义一个基类和多个子类,子类重写基类的方法来实现多态。以下是一个示例:
# 定义一个基类
class Animal:
def speak(self):
pass
# 定义子类 Dog,继承自 Animal
class Dog(Animal):
def speak(self):
return "Woof!"
# 定义子类 Cat,继承自 Animal
class Cat(Animal):
def speak(self):
return "Meow!"
# 定义一个函数,接受一个 Animal 类型的对象
def animal_speak(animal):
print(animal.speak())
# 创建 Dog 和 Cat 的实例
dog = Dog()
cat = Cat()
# 调用 animal_speak 函数,传入不同的对象
animal_speak(dog) # 输出: Woof!
animal_speak(cat) # 输出: Meow!
在上述代码中,Animal 是基类,Dog 和 Cat 是子类,它们都重写了 speak 方法。animal_speak 函数接受一个 Animal 类型的对象,并调用其 speak 方法。由于多态的存在,无论传入的是 Dog 还是 Cat 的实例,都能正确调用相应的 speak 方法。
6. 特殊属性和方法
1. 特殊属性
1. __dict__
- 类中:类的
__dict__是一个字典,包含了类的属性(类变量)和方法(函数定义)等。例如类方法、静态方法、类变量等都存储在这个字典中。通过它可以查看类所拥有的属性和方法定义。 - 实例对象中:实例的
__dict__也是一个字典,存储了实例的实例属性(即每个实例独有的属性)。每个实例的__dict__可以不同,因为实例属性的值可以不同。
class MyClass:
class_var = 10
def __init__(self):
self.instance_var = 20
print(MyClass.__dict__)
obj = MyClass()
print(obj.__dict__)
2.__class__
- 类中:在类的定义中,类本身没有
__class__属性,因为类就是最高层级的类型定义,不过可以通过type()函数来查看类的类型(如type(MyClass)会返回type)。 - 实例对象中:实例的
__class__属性返回该实例所属的类。通过这个属性可以获取实例的类型信息,以便进行类型检查或调用类的方法等操作。
class MyClass:
pass
obj = MyClass()
print(obj.__class__)
2. 特殊方法
1. __init__
- 类中:
__init__是类的特殊方法,定义在类中,用于在创建实例对象时初始化实例的属性。它不是构造函数(Python 中构造函数是__new__),而是初始化函数。类中定义了__init__方法后,创建实例时会自动调用。 - 实例对象中:
__init__方法在实例创建时被调用,用于设置实例的初始状态,例如为实例属性赋值。每个实例在创建时都会执行自己的__init__方法来初始化自身的属性。
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass(42)
print(obj.value)
2. __str__
- 类中:定义在类中的
__str__方法,用于返回一个人类可读的字符串表示该类的实例。当使用print()函数打印实例时,会调用实例的__str__方法。 - 实例对象中:每个实例在被
print()函数调用时,会执行自身的__str__方法来返回相应的字符串表示。不同实例可以有不同的__str__实现,以展示各自的状态信息。
class MyClass:
def __init__(self, value):
self.value = value
def __str__(self):
return f"MyClass with value {self.value}"
obj = MyClass(42)
print(obj)
3. __repr__
- 类中:在类中定义的
__repr__方法,返回一个对象的规范字符串表示,通常用于调试和开发阶段。它的返回值应该是一个可以用来重新创建该对象的字符串。 - 实例对象中:每个实例的
__repr__方法返回该实例的规范字符串表示。当在交互式环境中输入实例或使用repr()函数时,会调用实例的__repr__方法。
class MyClass:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"MyClass({self.value})"
obj = MyClass(42)
print(repr(obj))
4. __len__
- 类中:定义在类中的
__len__方法,用于指定对象的长度。当使用len()函数作用于该类的实例时,会调用实例的__len__方法。常用于模拟序列类型的行为。 - 实例对象中:每个实例在被
len()函数调用时,会执行自身的__len__方法来返回相应的长度值。不同实例可以根据自身的逻辑实现不同的长度计算方式。
class MyList:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
my_list = MyList([1, 2, 3])
print(len(my_list))
5. __getitem__
- 类中:在类中定义的
__getitem__方法,用于实现对象的索引访问操作(如obj[key])。它允许类的实例表现得像序列或映射类型。 - 实例对象中:每个实例在进行索引访问(如
obj[0])时,会执行自身的__getitem__方法来返回相应的元素或值。不同实例可以根据自身的数据结构和逻辑实现不同的索引访问行为。
class MyList:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
my_list = MyList([1, 2, 3])
print(my_list[1])
6. __setitem__
- 类中:类中定义的
__setitem__方法,用于实现对象的索引赋值操作(如obj[key] = value)。它配合__getitem__方法,可以使实例对象表现得像可变序列或映射类型。 - 实例对象中:每个实例在进行索引赋值操作时,会执行自身的
__setitem__方法来设置相应索引位置的值。不同实例可以根据自身的数据结构和逻辑实现不同的索引赋值行为。
class MyList:
def __init__(self, data):
self.data = data
def __setitem__(self, index, value):
self.data[index] = value
my_list = MyList([1, 2, 3])
my_list[1] = 4
print(my_list.data)
7. __delitem__
- 类中:在类中定义的
__delitem__方法,用于实现对象的索引删除操作(如del obj[key])。使实例对象可以支持删除指定索引位置的元素。 - 实例对象中:每个实例在执行
del语句进行索引删除操作时,会调用自身的__delitem__方法来删除相应索引位置的元素。不同实例可以根据自身的数据结构和逻辑实现不同的删除行为。
class MyList:
def __init__(self, data):
self.data = data
def __delitem__(self, index):
del self.data[index]
my_list = MyList([1, 2, 3])
del my_list[1]
print(my_list.data)
8. __iter__
- 类中:定义在类中的
__iter__方法,用于使类的实例成为可迭代对象。它应该返回一个迭代器对象(通常是实现了__next__方法的对象)。 - 实例对象中:每个实例在被用于
for循环或其他需要迭代的场景时,会调用自身的__iter__方法获取迭代器,然后通过迭代器来遍历实例中的元素。不同实例可以根据自身的数据结构和逻辑实现不同的迭代行为。
class MyList:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
result = self.data[self.index]
self.index += 1
return result
my_list = MyList([1, 2, 3])
for item in my_list:
print(item)
发表评论