Python类和对象
Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。
如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。
接下来我们先来简单的了解下面向对象的一些基本特征。
1. 面向对象编程 Object-Oriented Programming
-
什么是对象
- 对象是指现实中的物体或实体
-
什么是面向对象
- 把一切看成对象(实例),用各种对象之间的关系来描述事务。
-
对象都有什么特征
- 对象有很多属性(名词)
- 姓名, 年龄, 性别,
- 对象有很多属性(名词)
-
对象有很多行为(动作,动词)
- 学习,吃饭,睡觉,踢球, 工作
-
什么是类:
- 拥有相同属性和行为的对象分为一组,即为一个类
- 类是用来描述对象的工具,用类可以创建此类的对象(实例)
2.self
大家学Python面向对象的时候,总会遇到一个让人难以理解的存在:self
这个self到底是谁啊,为什么每个类实例方法都有一个参数self,它到底有什么作用呢?
「先下结论:类实例化后,self即代表着实例(对象)本身」
想要理解self有个最简单的方法,就是你把self当做**「实例(对象****)****的身份证」**。
类比人类,人类就是一个Python类,每个个体的人代表着实例(对象),而每个人的身份证代表的Python中self,每个人可以凭借身份证去上大学、坐高铁、住酒店…(方法),而Python中的实例(对象)也可以凭着self去调用类的方法。
-
self 是类方法的第一个参数,用于引用对象本身。
-
self 不是Python关键字,但是约定俗成的命名,可以使用其他名称代替,但通常不建议。
3. 属性和方法
3.1 类属性
-
类属性是类的属性,此属性属于类,不属于此类的实例
-
作用:
- 通常用来存储该类创建的对象的共有属性
-
类属性说明
- 类属性,可以通过该类直接访问
- 类属性,可以通过类的实例直接访问
-
类属性示例
class Human:
total_count = 0 # 创建类属性
def __init__(self, name):
self.name = name
print(Human.total_count)
h1 = Human("小张")
print(h1.total_count)
3.2 类方法
- 类方法是用于描述类的行为的方法,类方法属于类,不属于该类创建的对象
- 说明
- 类方法需要使用@classmethod装饰器定义
- 类方法至少有一个形参,第一个形参用于绑定类,约定写为’cls’
- 类和该类的实例都可以调用类方法
- 类方法不能访问此类创建的对象的实例属性
class A:
v = 0
@classmethod
def set_v(cls, value):
cls.v = value
@classmethod
def get_v(cls):
return cls.v
print(A.get_v())
A.set_v(100)
print(A.get_v())
a = A()
print(a.get_v())
class MyClass:
class_attr = 0 # 类属性
def __init__(self, value):
self.instance_attr = value # 实例属性
@classmethod
def modify_class_attr(cls, new_value):
cls.class_attr = new_value
print(f"类属性已修改为: {cls.class_attr}")
@classmethod
def try_modify_instance_attr(cls):
try:
cls.instance_attr = 10 # 尝试修改实例属性(会失败)
except AttributeError as e:
print(f"错误: {e}")
def show_attrs(self):
print(f"实例属性: {self.instance_attr}")
print(f"类属性: {self.class_attr}")
# 创建类的实例
obj = MyClass(5)
# 调用类方法修改类属性
MyClass.modify_class_attr(20) # 输出: 类属性已修改为: 20
obj.show_attrs()
# 输出:
# 实例属性: 5
# 类属性: 20
# 调用类方法尝试修改实例属性
MyClass.try_modify_instance_attr()
# 输出: 错误: type object 'MyClass' has no attribute 'instance_attr'
# 尝试调用类方法修改实例属性
obj.try_modify_instance_attr()
# 输出: 错误: type object 'MyClass' has no attribute 'instance_attr'
cls
在Python中, cls 是一个约定俗成的名称,用于表示类本身。在类方法(使用 @classmethod 装饰的
方法)中, cls 作为第一个参数传递给方法。这使得类方法可以访问和修改类属性以及调用其他类方
法,而不需要引用具体的实例。
cls 的作用
-
访问类属性:类方法可以通过 cls 访问和修改类属性。
-
调用类方法:类方法可以通过 cls 调用其他类方法。
-
创建类实例:类方法可以使用 cls 来创建类的实例。
class MyClass:
class_attr = 0 # 类属性
def __init__(self, value):
self.instance_attr = value # 实例属性
@classmethod
def modify_class_attr(cls, new_value):
cls.class_attr = new_value
print(f"类属性已修改为: {cls.class_attr}")
@classmethod
def show_class_attr(cls):
print(f"类属性当前值: {cls.class_attr}")
@classmethod
def create_instance(cls, value):
# 使用 cls 创建类实例
return cls(value)
# 调用类方法修改类属性
MyClass.modify_class_attr(20) # 输出: 类属性已修改为: 20
# 调用类方法显示类属性
MyClass.show_class_attr() # 输出: 类属性当前值: 20
# 使用类方法创建类的实例
new_instance = MyClass.create_instance(10)
print(f"新实例的实例属性: {new_instance.instance_attr}") # 输出: 新实例的实例属性: 10
print(f"新实例的类属性: {new_instance.class_attr}") # 输出: 新实例的类属性: 20
3.3 静态方法 @staticmethod
-
静态方法是定义在类的内部函数,此函数的作用域是类的内部
-
说明
- 静态方法需要使用@staticmethod装饰器定义
- 静态方法与普通函数定义相同,不需要传入self实例参数和cls类参数
- 静态方法只能凭借该类或类创建的实例调用
- 静态方法不能访问类属性和实例属性
class A:
class_attr = 42 # 类属性
def __init__(self, value):
self.instance_attr = value # 实例属性
@staticmethod
def myadd(a, b):
# 只能访问传递的参数,不能访问类属性和实例属性
return a + b
# 创建类实例
a = A(10)
# 调用静态方法
print(A.myadd(100, 200)) # 输出: 300
print(a.myadd(300, 400)) # 输出: 700
# 尝试在静态方法内访问类属性或实例属性(会导致错误)
class B:
class_attr = 42
def __init__(self, value):
self.instance_attr = value
@staticmethod
def myadd(a, b):
# 以下访问会导致错误
# return a + b + B.class_attr
# return a + b + self.instance_attr
return a + b
# 创建类实例
b = B(10)
# 调用静态方法
print(B.myadd(100, 200)) # 输出: 300
print(b.myadd(300, 400)) # 输出: 700
**3.4 **魔术方法
Python中的魔术方法(Magic Methods)是一种特殊的方法,它们以双下划线开头和结尾,例如__init__ , str , add 等。这些方法允许您自定义类的行为,以便与内置Python功能(如
+运算符、迭代、字符串表示等)交互。
以下是一些常用的Python魔术方法:
-
init(self, …) : 初始化对象,通常用于设置对象的属性。
-
str(self) : 定义对象的字符串表示形式,可通过 str(object) 或 print(object) 调用。例如,您可以返回一个字符串,描述对象的属性。
-
repr(self) : 定义对象的“官方”字符串表示形式,通常用于调试。可通过 repr(object) 调用。
-
len(self) : 定义对象的长度,可通过 len(object) 调用。通常在自定义容器类中使用。
-
getitem(self, key) : 定义对象的索引操作,使对象可被像列表或字典一样索引。例如,object[key] 。
-
setitem(self, key, value) : 定义对象的赋值操作,使对象可像列表或字典一样赋值。例如, object[key] = value 。
-
delitem(self, key) : 定义对象的删除操作,使对象可像列表或字典一样删除元素。例如,del object[key] 。
-
iter(self) : 定义迭代器,使对象可迭代,可用于 for 循环。
-
next(self) : 定义迭代器的下一个元素,通常与 iter 一起使用。
-
add(self, other) : 定义对象相加的行为,使对象可以使用 + 运算符相加。例如, object1+ object2 。
-
sub(self, other) : 定义对象相减的行为,使对象可以使用 - 运算符相减。
-
eq(self, other) : 定义对象相等性的行为,使对象可以使用 == 运算符比较。
-
lt(self, other) : 定义对象小于其他对象的行为,使对象可以使用 < 运算符比较。
-
gt(self, other) : 定义对象大于其他对象的行为,使对象可以使用 > 运算符比较
4. 继承/****派生
-
什么是继承/派生
- 继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
- 派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
-
为什么继承/派生
- 继承的目的是延续旧的类的功能
- 派生的目地是在旧类的基础上添加新的功能
-
继承/派生的作用
- 用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
- 在不改变基类的代码的基础上改变原有类的功能
-
继承/派生名词:
- 基类(base class)/超类(super class)/父类(father class)
- 派生类(derived class)/子类(child class)
4.1 多继承
Python支持多继承形式。
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
#另一个类,多继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))
#多继承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)
test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法
4.2 覆盖 override
-
覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类的实例调用该方法时,实际调用的是子类中的覆盖版本,这种现象叫覆盖
-
作用:
- 实现和父类同名,但功能不同的方法
5. 封装 enclosure
-
封装是指隐藏类的实现细节,让使用者不用关心这些细节;
-
封装的目的是让使用者通过尽可能少的方法(或属性)操作对象
-
Python的封装是假的(模拟的)封装
-
私有属性和方法
-
python类中以双下划线( __ )开头,不以双下划线结尾的标识符为私有成员,私有成员只能使用方法来进行访问和修改
-
以 __ 开头的属性为类的私有属性,在子类和类外部无法直接使用
-
以 __ 开头的方法为私有方法,在子类和类外部无法直接调用
-
-
class A:
def __init__(self):
self.__p1 = 100 # 私有属性
def __m1(self): # 私有方法
print("__m1(self) 方法被调用")
def showA(self):
self.__m1()
print("self.__p1 = ", self.__p1)
class B(A):
def __init__(self):
super().__init__()
def showB(self):
self.__m1() # 出错,不允许调用
print("self.__p1 = ", self.__p1) # 出错,不允许调用
# self._A__m1() # 正常调用
# print("self.__p1 =", self._A__p1) # 正常访问
a = A()
a.showA()
a.__m1() # 出错,不允许调用
v = self.__p1 # 出错,不允许调用
b = B()
b.showB()
# 访问私有属性
print(a._A__p1) # 输出: 100
# 调用私有方法
a._A__m1() # 输出: __m1(self) 方法被调用
# 不推荐了解就行
6. 多态 polymorphic
-
什么是多态:
- 字面意思"多种状态"
- 多态是指在有继承/派生关系的类中,调用基类对象的方法,实际能调用子类的覆盖方法的现象叫多态
-
状态:
- 静态(编译时状态)
- 动态(运行时状态)
-
多态说明:
- 多态调用的方法与对象相关,不与类型相关
- Python的全部对象都只有"运行时状态(动态)“, 没有"C++语言"里的"编译时状态(静态)”
class Shape:
def draw(self):
print("Shape的draw()被调用")
class Point(Shape):
def draw(self):
print("正在画一个点!")
class Circle(Point):
def draw(self):
print("正在画一个圆!")
def my_draw(s):
s.draw() # 此处显示出多态
shapes1 = Circle()
shapes2 = Point()
my_draw(shapes1) # 调用Circle 类中的draw
my_draw(shapes2) # Point 类中的draw
- 面向对象编程语言的特征:
- 继承
- 封装
- 多态
7. 方法重写
如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法
函数重写
- 在自定义类内添加相应的方法,让自定义类创建的实例像内建对象一样进行内建函数操作
对象转字符串函数重写
- 对象转字符串函数重写方法
- str() 函数的重载方法:def str(self)
- 如果没有 str(self) 方法,则返回repr(obj)函数结果代替
- str() 函数的重载方法:def str(self)
class MyNumber:
"此类用于定义一个自定义的类,用于演示str/repr函数重写"
def __init__(self, value):
"构造函数,初始化MyNumber对象"
self.data = value
def __str__(self):
"转换为普通字符串"
return "%s" % self.data
n1 = MyNumber("一只猫")
n2 = MyNumber("一只狗")
print("str(n2) ===>", str(n2))
内建函数重写
-
abs abs(obj) 函数调用
-
len len(obj) 函数调用
-
reversed reversed(obj) 函数调用
-
round round(obj) 函数调用
# file : len_overwrite.py
class MyList:
def __init__(self, iterable=()):
self.data = [x for x in iterable]
def __repr_(self):
return "MyList(%s)" % self.data
def __len__(self):
print("__len__(self) 被调用!")
return len(self.data)
def __abs__(self):
print("__len__(self) 被调用!")
return MyList((abs(x) for x in self.data))
myl = MyList([1, -2, 3, -4])
print(len(myl))
print(abs(myl))
运算符重载
-
运算符重载是指让自定义的类生成的对象(实例)能够使用运算符进行操作
-
运算符重载的作用
- 让自定义类的实例像内建对象一样进行运算符操作
- 让程序简洁易读
- 对自定义对象将运算符赋予新的运算规则
-
运算符重载说明:
- 运算符重载方法的参数已经有固定的含义,不建议改变原有的意义
方法名 | 运算符和表达式 | 说明 |
---|---|---|
add(self, rhs) | self + rhs | 加法 |
sub(self, rhs) | self - rhs | 减法 |
mul(self, rhs) | self * rhs | 乘法 |
truediv(self, rhs) | self / rhs | 除法 |
floordiv(self, rhs) | self // rhs | 地板除 |
mod(self, rhs) | self % rhs | 取模(求余) |
pow(self, rhs) | self ** rhs | 幂 |
注释:rhs (right hand side)右手边
class MyNumber:
"此类用于定义一个自定义的类,用于演示运算符重载"
def __init__(self, value):
"构造函数,初始化MyNumber对象"
self.data = value
def __str__(self):
"转换为表达式字符串"
return "MyNumber(%d)" % self.data
def __add__(self, rhs):
"加号运算符重载"
print("__add__ is called")
return MyNumber(self.data + rhs.data)
def __sub__(self, rhs):
"减号运算符重载"
print("__sub__ is called")
return MyNumber(self.data - rhs.data)
n1 = MyNumber(100)
n2 = MyNumber(200)
print(n1 + n2)
print(n1 - n2)
8. super函数
super() 函数是用于调用父类(超类)的一个方法。
super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
super() 方法的语法:
在子类方法中可以使用super().add()****调用父类中已被覆盖的方法
可以使用super(Child, obj).myMethod()****用子类对象调用父类已被覆盖的方法
class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')
c = Child()# 子类实例
c.myMethod()# 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
super().init()
super().init() 是 Python 中用于调用父类(基类)构造函数的一种方式。它通常用于子类的构造函数中,以确保父类的构造函数被正确调用和初始化。这在继承(inheritance)中尤为重要,因为父类的初始化代码可能包含设置实例变量或执行其他重要的初始化任务。
class Parent:
def __init__(self):
print("Parent class constructor called")
self.parent_attribute = "I am a parent attribute"
class Child(Parent):
def __init__(self):
super().__init__()
print("Child class constructor called")
self.child_attribute = "I am a child attribute"
# 创建一个 Child 类的实例
child_instance = Child()
# 输出
# Parent class constructor called
# Child class constructor called
注释
- Parent 类:
定义了一个构造函数 init() ,在构造函数中打印了一条消息,并初始化了一个属性
parent_attribute 。
- Child 类:
-
继承自 Parent 类。
-
在其构造函数 init() 中,首先调用了 super().init() 。这行代码会调用Parent 类的构造函数,确保 Parent 类的初始化逻辑被执行。
-
然后打印了一条消息,并初始化了一个属性 child_attribute 。
- 实例化 Child 类:
- 创建 Child 类的实例时,首先执行 Parent 类的构造函数,打印 “Parent class constructor called”,然后执行 Child 类的构造函数,打印 “Child class constructor called”。
为什么使用 super().init() ?
-
代码重用:避免在子类中重复父类的初始化代码。
-
正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行。
-
支持多重继承:在多重继承情况下, super() 可以确保所有基类的构造函数都被正确调用。