当前位置:   article > 正文

python面向对象解析(简单易懂版)

python面向对象

Python从设计之初就已经是一门面向对象的语言,正因如此,在Python中创建一个类和对象是很容易的。面向对象编程(Object-Oriented Programming, OOP)是一种程序设计范式,它将现实世界中的实体抽象成类,并通过实例化这些类来创建具体对象。在Python中,面向对象有三个核心概念:封装、继承和多态;我们在此篇分开讲解。

目录

面向对象技术简介

一、类(class)&方法(method)

1、实例方法(Instance Method)

2、类方法(Class Method)

3、静态方法(Static Method)

二、对象(Object)

实例化

_ _init_ _()(构造方法)

self是什么

三、封装

概念

成员保护

单下划线开头的变量(_var): 

双下划线开头的变量(__var):

@property装饰器创建只读属性:

四、继承

五、多态


面向对象技术简介

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量:定义在方法中的变量,只作用于当前实例的类。
  • 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 继承:即一个子类继承父类的字段和方法。继承也允许把一个子类的对象作为一个父类对象对待。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

一、类(class)&方法(method)

0、类的定义

类是创建对象的蓝图或模板,它描述了一组具有相同属性和行为的对象集合。通过定义类,可以设定对象的数据结构(即属性)以及操作数据的方法。

举个例子:

  1. class ClassName:
  2. # 类体部分
  3. def __init__(self, attr1, attr2): # 初始化方法(构造函数)
  4. self.attr1 = attr1 # 定义实例属性
  5. self.attr2 = attr2
  6. def method_name(self, arg1): # 定义类里的方法
  7. # 对象的方法实现
  8. pass
'
运行
除了类里定义的方法,还有实例方法(Instance Method)、类方法(Class Method)、静态方法(Static Method),我们逐一拓展了解一下:

1、实例方法(Instance Method)

实例方法与特定的类实例相关联,它默认接收一个隐含的参数self作为第一个参数,这个self指向调用该方法的对象本身。

举个生活例子:假设我们有一个“手机”类,它有一个make_call的实例方法。当你拥有一部具体的手机(例如iPhone),你可以通过这部iPhone拨打电话(调用make_call方法)。这里的iPhone就是类“手机”的一个实例。

  1. class Phone:
  2. def __init__(self, brand):
  3. self.brand = brand
  4. # 实例方法
  5. def make_call(self, number):
  6. print(f"使用 {self.brand} 打电话给: {number} ...")
  7. my_phone = Phone("iPhone")
  8. my_phone.make_call("12345678") # 调用实例方法 # 输出结果:使用 iPhone 打电话给: 12345678 ...
'
运行

在这个例子中,make_call()是一个实例方法,因为它依赖于具体的my_phone实例(例如"iphone"),并通过self引用了实例的brand属性。

2、类方法(Class Method)

类方法与类关联而不是实例,它需要使用装饰器@classmethod进行标识,并且其第一个参数通常命名为cls,代表调用它的类本身。

举个生活例子:继续以手机为例,如果有一个类方法get_default_brand,用于返回该品牌手机的默认型号。不论你拥有哪款手机,都可以通过手机品牌类直接获取其默认型号。

  1. class Phone:
  2. @classmethod
  3. def get_default_brand(cls):
  4. return "HUAWEI MATE 60 Pro"
  5. # 不需要实例化Phone类就可以调用类方法
  6. default_model = Phone.get_default_brand()
  7. print(default_model) # 输出结果:HUAWEI MATE 60 Pro
'
运行

在上面的例子中,get_default_brand()是一个类方法,它可以用来返回该品牌手机的默认型号,无需先创建一个实例就可以直接通过类名调用。

3、静态方法(Static Method)

静态方法不依赖于类或类实例,它与类的关系比类方法更弱,更像是独立的函数,但为了组织代码方便,将它们放在类的命名空间下。静态方法使用装饰器@staticmethod进行标识,不需要任何特殊的参数。

举个生活例子:假设我们在手机商店类中有一个静态方法calculate_price,用来计算购买多部手机的总价格,无论买的是什么品牌的手机,也不需知道具体的手机实例,只和购买的数量和单价有关。

  1. class MobileShop:
  2. @staticmethod
  3. def calculate_price(quantity, unit_price):
  4. return quantity * unit_price
  5. # 不需要实例化MobileShop类就可以调用静态方法
  6. total_cost = MobileShop.calculate_price(2, 8000)
  7. print(total_cost) # 输出:16000
'
运行
上述例子中的 calculate_price()就是一个静态方法,它并不需要知道任何关于MobileShop类实例的信息,只是提供了一个简单的计算功能,与类本身无关。
小结
1.定义形式上:
类方法和静态方法都是通过装饰器实现的,实例方法不是;实例方法需要传入self参数,类方法需要传入cls参数,而静态方法不需要传self或者cls参数。
2. 调用方式上:
实例方法只能通过实例对象调用;类方法和静态方法可以通过类对象或者实例对象调用,如果是使用实例对象调用的类方法或静态方法,最终都会转而通过类对象调用。
3. 应用场景:
实例方法使用最多,可以直接处理实例对象的逻辑;类方法不需要创建实例对象,直接处理类对象的逻辑;静态方法将与类对象相关的某些逻辑抽离出来,不仅可以用于测试,还能便于代码后期维护。
实例方法和类方法,能够改变实例对象或类对象的状态,而静态方法不能

二、对象(Object)

实例化

为类创建的具体实例称为类对象。通过调用类名后跟括号()并传入初始化方法所需的参数来创建类对象。

  • 类对象支持两种操作:属性引用和实例化。
  • 属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name。
  • 类对象创建后,类命名空间中所有的命名都是有效属性名。
  1. class MyClass:
  2. """一个简单的类实例"""
  3. i = 123
  4. def func(self):
  5. return 'hi,are you ok'
  6. # 实例化类
  7. x = MyClass()
  8. # 访问类的属性和方法
  9. print("MyClass 类的属性 i 为:", x.i)
  10. print("MyClass 类的方法 f 输出为:", x.func())
  11. #以上创建了一个新的类实例并将该对象赋给局部变量 x,x 为空的对象。
  12. #输出:
  13. '''
  14. MyClass 类的属性 i 为: 123
  15. MyClass 类的方法 f 输出为: hi,are you ok
  16. '''
'
运行
类中的数据,和实例中的数据,是什么关系?
访问实例数据时,会先从实例的 命名空间 中寻找, 如果实例的命名空间 没有 ,就从类的命名空间 中寻找。
  1. class A:
  2. N=0
  3. def xy(self):
  4. A.N=123
  5. a=A()
  6. b=A()
  7. print(a.N) # 输出结果:0
  8. print(b.N) # 输出结果:0
  9. print(A.N) # 输出结果:0
  10. #此时a.N和b.N的值都为0,都是使用类的命名空间,调用了类的属性N=0
  11. b.N=100 # b创建了N=100,接下来b调用的N是 b实例的命名空间里的N
  12. print(a.N) # 输出结果:0
  13. print(b.N) # 输出结果:100
  14. print(A.N) # 输出结果:0
  15. a.xy() # a实例调用xy()方法,修改了类的命名空间里的属性N=123
  16. print(a.N) # 输出结果:123
  17. print(b.N) # 输出结果:100
  18. print(A.N) # 输出结果:123
  19. #此时a使用的是类的命名空间,a.N、A.N输出的都是123,b使用的是自己实例的命名空间
'
运行

_ _init_ _()(构造方法

类定义了 _ _init_ _() 方法,类的实例化操作就会自动调用 _ _init_ _() 方法。_ _init_ _() 方法可以有参数,参数通过 _ _init_ _() 传递到类的实例化操作上。例如:

  1. class number_data:
  2. def __init__(self, int_data, float_data):
  3. self.i = int_data
  4. self.f = float_data
  5. x = number_data(3, 4.5)
  6. print(x.i, x.f) # 输出结果:3 4.5
'
运行
dataclass 饰器, 自动为 class 添加 了很多 特殊 方法 ,包括 __init__,使用该装饰器 不需要 定义 __init__也能实现功__init__的功能,使用规则如下例:
  1. import dataclasses
  2. @dataclasses.dataclass
  3. class People:
  4. name: str = "" # 前提:必须为属性,声明类型,否则会报错
  5. age: int = 0
  6. sex: str = ""
  7. def show(self):
  8. print(self.name)
  9. print(self.age)
  10. print(self.sex)
  11. a = People("小石", 19, "男")
  12. a.show() # 输出结果:小石 19 男
'
运行

self是什么

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例该参数的名称是 self。

  1. #例1
  2. class myclass:
  3. def pr(self):
  4. print(self)
  5. print(self.__class__)
  6. t = myclass()
  7. t.pr()
  8. #输出
  9. '''
  10. <__main__.myclass object at 0x000002392516FFD0>
  11. <class '__main__.myclass'>
  12. '''
  13. #例2
  14. class people:
  15. # 定义基本属性
  16. name = ''
  17. age = 0
  18. # 定义构造方法
  19. def __init__(self, n, a, ):
  20. self.name = n
  21. self.age = a
  22. def speak(self):
  23. print("%s 说: 我 %d 岁。" % (self.name, self.age))
  24. # 实例化类
  25. p = people('小石', 18)
  26. p.speak() # 输出结果:小石 说: 我 18 岁。
  27. '''
  28. 在类的内部,使用def关键字来定义一个方法,与一般函数定义不同,
  29. 类方法必须包含参数self, 且为第一个参数,self 代表的是类的实例。
  30. '''
'
运行

从上述例1执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。self 不是 python 关键字,我们把他换成别的字符串也是可以正常执行的:

  1. class myclass:
  2. def pr(me):
  3. print(me)
  4. print(me.__class__)
  5. t = myclass()
  6. t.pr()
  7. #输出
  8. '''
  9. <__main__.myclass object at 0x000002392516FFD0>
  10. <class '__main__.myclass'>
  11. '''
'
运行

三、封装

概念

封装是把数据(属性或字段)和对这些数据的操作(方法)捆绑在一起,并隐藏内部实现细节的过程。通俗地讲,封装就像一个“黑箱”,我们只需要知道怎么使用这个箱子(调用接口),而不需要了解箱子内部是如何工作的。在Python中,类可以定义私有变量(通常通过双下划线前缀_ _var来表示),外部无法直接访问,只能通过类提供的公共方法进行操作,确保对象中的数据安全。

举个生活中的例子来解释封装:

遥控器内部有很多复杂的电子元件和电路(这些是“数据”),但我们并不需要知道它们是如何工作的。我们只需要通过按钮(“方法”)来操作它,比如按下开关按钮、调节音量等。

  1. class RemoteController: #定义遥控器类
  2. # 内部复杂的数据结构(类似电子元件和电路)
  3. __battery_level = 100 # 私有属性,外部无法直接访问或修改
  4. def __init__(self):
  5. self.__turn_on_tv() # 初始化时打开电视
  6. # 对外提供的操作接口(方法)
  7. def turn_on(self):
  8. self.__operate_tv("打开电视机")
  9. def turn_off(self):
  10. self.__operate_tv("关闭电视机")
  11. def change_volume(self, volume):
  12. self.__set_volume(volume)
  13. # 内部实现细节(不直接暴露给外部)
  14. def __turn_on_tv(self):
  15. print("TV is turning on...")
  16. def __operate_tv(self, action):
  17. print(f"Operating TV: {action}")
  18. def __set_volume(self, volume):
  19. if 0 <= volume <= 100:
  20. print(f"Setting volume to {volume}")
  21. else:
  22. print("Invalid volume level!")
  23. # 使用者视角:
  24. remote = RemoteController()
  25. remote.turn_on() # 按下开关键
  26. remote.change_volume(50) # 调整音量至50
'
运行

在这个例子中,定义了一个名为RemoteController的遥控器类,该遥控器类具有以下功能:

  • 1、内部具有复杂的数据结构,包括私有属性__battery_level表示电池电量。
  • 2、在初始化时,会自动调用__turn_on_tv()方法打开电视。
  • 3、提供对外的操作接口,包括打开电视(turn_on)、关闭电视(turn_off)和调节音量(change_volume)。
  • 4、内部实现了具体的电视操作细节,包括打开电视(__turn_on_tv)、操作电视(__operate_tv)和设置音量(__set_volume)。

其中,__turn_on_tv()、__operate_tv()和__set_volume()方法为私有方法,只能在类的内部调用,不能直接从外部访问或调用。而turn_on()、turn_off()和change_volume()方法为公有方法,可以从类的外部调用,提供了对外的操作接口。

成员保护

1)  _ 下划线开头的变量,受保护的成员,不建议直访问和修改,允许在类内部进行访问和修改
2)_ _ 双下划线,受保护的成员,不能直接访问和修改,允许在本类内部进行访问和修改
3)@property 装饰器:是一个函数伪装成属性,调用时不需要加括号;我们可以使用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。

单下划线开头的变量(_var): 

生活例子:假设你有一本私人日记,你不想让所有人都看到,但允许家人在必要时查看。日记本就像是一个类,而日记内容是其中的一个变量 _content。虽然不鼓励外人直接翻看你的日记,但在家庭内部,父母或兄弟姐妹可以出于关心询问并阅读。

  1. class Diary:
  2. def __init__(self, content):
  3. self._content = content
  4. def share_with_family(self):
  5. return self._content # 家庭成员可以通过这个方法来读取日记内容
  6. daliy=Diary("今天天气真好!")
  7. print(daliy.share_with_family()) # 输出结果:今天天气真好!
  8. # 虽然按照约定不应该直接访问,但仍然可以这样做
  9. print(daliy._content) # 输出结果:今天天气真好!
'
运行

双下划线开头的变量(__var):

 生活例子:现在设想你有一个保险箱,它有一个非常私密的四位数密码 __password,除了你自己,任何人都不能知道或更改。即使是在保险箱内部,密码也不会被轻易公开,而是通过一种安全的方式来验证和修改密码。

  1. class Safe:
  2. def __init__(self, password):
  3. self.__password = password
  4. #定义验证修改密码方法
  5. def verify_and_change_password(self, old_password, new_password):
  6. if self.__password == old_password:
  7. self.__password = new_password
  8. print(f"密码修改成功,新密码为:{self.__password}")
  9. else:
  10. print("密码修改失败,旧密码输入有误.")
  11. obj=Safe("123456")
  12. print(obj.__password)
  13. '''直接访问会报错
  14. print(obj.__password)
  15. AttributeError: 'Safe' object has no attribute '__password'
  16. '''
  17. # 但实际上仍可通过原名访问,但这违反了封装原则
  18. print(obj._Safe__password) # 输出: 123456,但这并不是推荐的做法
  19. obj.verify_and_change_password("123456", "654321")#修改密码 输出 密码修改成功,新密码为:654321
  20. obj.verify_and_change_password("123456", "654321")#修改密码 输出 密码修改失败,旧密码输入有误.
  21. obj.verify_and_change_password("654321", "111111")#修改密码 输出 密码修改成功,新密码为:111111
  22. # 强行修改
  23. obj.__password = "123321"
  24. print(obj.__password) #强行修改后可以直接访问到属性值,
  25. #这里尝试直接修改__password属性时,
  26. #Python解释器会将__password改写为 _Safe__password(即在类名和原属性名间插入一个下划线),所以上述代码依然能够改变密码:
  27. #但是,直接修改私有变量违反了封装原则,应当避免这样做。在设计类时,采用getter和setter方法来控制对私有属性的访问是更好的做法。

@property装饰器创建只读属性:

1.修饰方法,使方法可以像属性一样访问。

2.与所定义的属性配合使用,这样可以防止属性被修改。

由于python进行属性的定义时,没办法设置私有属性,因此要通过@property的方法来进行设置。这样可以隐藏属性名,让用户进行使用的时候无法随意修改。

生活例子:想象一下你拥有一台智能冰箱,冰箱有一个显示屏显示当前内部温度。为了保证冰箱运行的安全和稳定,你不希望别人随意修改设定温度,所以将温度设置为只读属性,只能查看不能直接修改。

  1. class SmartFridge:
  2. def __init__(self, current_temperature):
  3. self.__current_temperature = current_temperature #初始化 当前温度 属性值
  4. @property
  5. def temperature(self): # 这是一个只读属性,用于获取当前温度
  6. return self.__current_temperature
  7. # 可以定义一个方法来调整温度,但不是直接修改temperature属性
  8. def set_temperature(self, new_temperature):
  9. if 0 <= new_temperature <= 10: # 假设合理温度范围为0-10度
  10. self.__current_temperature = new_temperature
  11. print(f"温度设置为{new_temperature}")
  12. else:
  13. print("温度设置无效")
  14. fridge = SmartFridge(3)
  15. print(fridge.temperature) # 输出:5
  16. fridge.set_temperature(6) # 正确做法是通过set_temperature方法调整温度
  17. print(fridge.temperature) # 输出:6
  18. fridge.set_temperature(11) # 输出:温度设置无效
  19. fridge.temperature = 5
  20. '''这将引发错误,因为我们已将其设置为只读属性
  21. fridge.temperature = 5
  22. AttributeError: can't set attribute 'temperature'
  23. '''

下划线和双下划线开头的命令方式,只是一种约定,没有强制作用。总结来说,遵循这些下划线约定有助于提高代码质量、可读性和维护性。虽然它们不是强制性的,但是为了遵循最佳实践并编写易于理解和协作的代码,建议在开发过程中遵循这些约定。

四、继承

继承是面向对象编程中的一个核心概念,它允许创建一个新类(称为子类或派生类),该类可以从已存在的类(称为父类或基类)中获取属性和方法。通俗来说,继承就像是现实生活中的一种“模板”或者“蓝图”的应用。

生活例子: 假设我们有一个基础的交通工具类,其中包含了所有交通工具共有的属性(如颜色、速度等)和方法(如启动、停止、加速等)。现在,我们创建一个新的汽车类,一个新的自行车类,分别都是特殊的交通工具,以及创建一个功能类。代码展示继承、多继承、重写父类方法如下:

  1. #定义一个交通工具类
  2. class Transportation:
  3. def __init__(self, color):
  4. self.color = color
  5. def start(self):
  6. print("启动")
  7. def stop(self):
  8. print("停止")
  9. def accelerate(self):
  10. print("踩油门加速")
  11. class func:
  12. def func1(self, name):
  13. print(f"{name}载人出行")
  14. #单继承
  15. class Car(Transportation):
  16. def __init__(self, color, wheels):
  17. super().__init__(color) # 使用 super() 调用父类的 __init__ 方法
  18. self.wheels = wheels # 为 Car 类添加 车轮 属性
  19. def horn(self):
  20. print(f"{self.color}汽车{self.wheels}个轮子")
  21. print("汽车滴滴响")
  22. car=Car("红色", 4)
  23. car.start() #继承父类的方法,输出:启动
  24. car.stop() #继承父类的方法,输出:停止
  25. car.accelerate() #继承父类的方法,输出:踩油门加速
  26. car.horn() #继承父类的方法,输出:红色汽车4个轮子
  27. #多继承+重写父类方法
  28. class bicycle(Transportation,func):
  29. def __init__(self, color, wheels):
  30. super().__init__(color) # 使用 super() 调用父类的 __init__ 方法
  31. self.wheels = wheels # 为 bicycle 类添加 车轮 属性
  32. print(f"{self.color}自行车{self.wheels}个轮子")
  33. def accelerate(self):
  34. print("使劲踩踏加速")
  35. bic=bicycle("黑色", 2) #实例化类,输出:黑色自行车2个轮子
  36. bic.start() #继承父类的Transportation方法,输出:启动
  37. bic.stop() #继承父类Transportation的方法,输出:停止
  38. bic.func1("自行车") #继承父类func方法,输出:自行车载人出行
  39. bic.accelerate() #重写父类Transportation方法,输出:使劲踩踏加速
'
运行

五、多态

 多态意味着同一个接口可以有不同的表现形式。在OOP中,这意味着子类可以覆盖或重写父类的方法,从而使得同一消息可以根据发送的对象类型产生不同的结果。同时,由于Python支持鸭子类型(Duck Typing),即“如果它走起路来像鸭子,叫起来也像鸭子,那它就是鸭子”,所以不同类型的对象只要具有相同的方法签名,就可以被同样的方式调用。

生活例子: 假设我们有一批电器设备,它们都有一个“开机”功能,但是具体怎么开机取决于设备的类型:

  1. 电视(TV):按下遥控器上的电源按钮,电视就会开启并显示画面。
  2. 电脑(computer):按一下主机上的开机键,电脑会启动并显示画面。
  3. 电灯(Light):旋动开关或者通过智能灯泡APP点击开灯按钮,电灯亮起。

程序设计中,我们可以抽象出一个ElectricalDevice基类,其中有一个通用的turn_on()方法。然后定义TVcomputerLight三个子类,分别重写turn_on()方法以实现各自设备的开机行为。当调用任意一个设备实例的turn_on()方法时,执行的就是对应类型的开机操作。这就是多态的体现——通过统一的接口(方法名),实现了不同对象的不同表现形式。

  1. class TV(ElectricalDevice):
  2. def turn_on(self):
  3. print("按下遥控器上的电源按钮,电视就会开启并显示画面")
  4. class Light(ElectricalDevice):
  5. def turn_on(self):
  6. print("按下墙上的开关,灯就会亮")
  7. class computer(ElectricalDevice):
  8. def turn_on(self):
  9. print("按一下主机上的电源按钮,电脑就会开机")
  10. def turn_on(devices):
  11. devices.turn_on() # 传入的对象不同,turn_on()方法对应的操作和现象也不同
  12. turn_on(TV()) # 输出:按下遥控器上的电源按钮,电视就会开启并显示画面
  13. turn_on(Light()) # 输出:按下墙上的开关,灯就会亮
  14. turn_on(computer()) # 输出:按一下主机上的电源按钮,电脑就会开机
  • 多态保证了代码的灵活性
  • 忽略对象是实际类型,而是以胜任的方式充当任意类型,一个对象可以以不同的形态去呈现
  • 多态是方法的多态,属性没有多态
  • 多态的存在有 2 个必要条件:继承、方法重写

总结来说,面向对象编程的核心在于通过模拟现实世界的实体和关系,建立模型,并利用封装、继承和多态等机制组织代码,使程序更加清晰、模块化和易于维护。希望这篇文章能帮助友友们快速理解弄懂面向对象。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/人工智能uu/article/detail/952750
推荐阅读
相关标签
  

闽ICP备14008679号