赞
踩
Python从设计之初就已经是一门面向对象的语言,正因如此,在Python中创建一个类和对象是很容易的。面向对象编程(Object-Oriented Programming, OOP)是一种程序设计范式,它将现实世界中的实体抽象成类,并通过实例化这些类来创建具体对象。在Python中,面向对象有三个核心概念:封装、继承和多态;我们在此篇分开讲解。
目录
类是创建对象的蓝图或模板,它描述了一组具有相同属性和行为的对象集合。通过定义类,可以设定对象的数据结构(即属性)以及操作数据的方法。
举个例子:
- class ClassName:
- # 类体部分
- def __init__(self, attr1, attr2): # 初始化方法(构造函数)
- self.attr1 = attr1 # 定义实例属性
- self.attr2 = attr2
-
- def method_name(self, arg1): # 定义类里的方法
- # 对象的方法实现
- pass
'运行
实例方法与特定的类实例相关联,它默认接收一个隐含的参数self
作为第一个参数,这个self
指向调用该方法的对象本身。
举个生活例子:假设我们有一个“手机”类,它有一个make_call的实例方法。当你拥有一部具体的手机(例如iPhone),你可以通过这部iPhone拨打电话(调用make_call方法)。这里的iPhone就是类“手机”的一个实例。
- class Phone:
- def __init__(self, brand):
- self.brand = brand
- # 实例方法
- def make_call(self, number):
- print(f"使用 {self.brand} 打电话给: {number} ...")
- my_phone = Phone("iPhone")
- my_phone.make_call("12345678") # 调用实例方法 # 输出结果:使用 iPhone 打电话给: 12345678 ...
'运行
在这个例子中,make_call()
是一个实例方法,因为它依赖于具体的my_phone实例(例如"iphone"),并通过self
引用了实例的brand
属性。
类方法与类关联而不是实例,它需要使用装饰器@classmethod
进行标识,并且其第一个参数通常命名为cls
,代表调用它的类本身。
举个生活例子:继续以手机为例,如果有一个类方法get_default_brand
,用于返回该品牌手机的默认型号。不论你拥有哪款手机,都可以通过手机品牌类直接获取其默认型号。
- class Phone:
- @classmethod
- def get_default_brand(cls):
- return "HUAWEI MATE 60 Pro"
-
- # 不需要实例化Phone类就可以调用类方法
- default_model = Phone.get_default_brand()
- print(default_model) # 输出结果:HUAWEI MATE 60 Pro
'运行
在上面的例子中,get_default_brand()
是一个类方法,它可以用来返回该品牌手机的默认型号,无需先创建一个实例就可以直接通过类名调用。
静态方法不依赖于类或类实例,它与类的关系比类方法更弱,更像是独立的函数,但为了组织代码方便,将它们放在类的命名空间下。静态方法使用装饰器@staticmethod
进行标识,不需要任何特殊的参数。
举个生活例子:假设我们在手机商店类中有一个静态方法calculate_price
,用来计算购买多部手机的总价格,无论买的是什么品牌的手机,也不需知道具体的手机实例,只和购买的数量和单价有关。
- class MobileShop:
- @staticmethod
- def calculate_price(quantity, unit_price):
- return quantity * unit_price
- # 不需要实例化MobileShop类就可以调用静态方法
- total_cost = MobileShop.calculate_price(2, 8000)
- print(total_cost) # 输出:16000
'运行
calculate_price()
就是一个静态方法,它并不需要知道任何关于MobileShop类实例的信息,只是提供了一个简单的计算功能,与类本身无关。
小结
1.定义形式上:
类方法和静态方法都是通过装饰器实现的,实例方法不是;实例方法需要传入self参数,类方法需要传入cls参数,而静态方法不需要传self或者cls参数。
2. 调用方式上:
实例方法只能通过实例对象调用;类方法和静态方法可以通过类对象或者实例对象调用,如果是使用实例对象调用的类方法或静态方法,最终都会转而通过类对象调用。
3. 应用场景:
实例方法使用最多,可以直接处理实例对象的逻辑;类方法不需要创建实例对象,直接处理类对象的逻辑;静态方法将与类对象相关的某些逻辑抽离出来,不仅可以用于测试,还能便于代码后期维护。
实例方法和类方法,能够改变实例对象或类对象的状态,而静态方法不能
为类创建的具体实例称为类对象。通过调用类名后跟括号()
并传入初始化方法所需的参数来创建类对象。
- class MyClass:
- """一个简单的类实例"""
- i = 123
- def func(self):
- return 'hi,are you ok'
- # 实例化类
- x = MyClass()
- # 访问类的属性和方法
- print("MyClass 类的属性 i 为:", x.i)
- print("MyClass 类的方法 f 输出为:", x.func())
-
- #以上创建了一个新的类实例并将该对象赋给局部变量 x,x 为空的对象。
- #输出:
- '''
- MyClass 类的属性 i 为: 123
- MyClass 类的方法 f 输出为: hi,are you ok
- '''
'运行
- class A:
- N=0
- def xy(self):
- A.N=123
- a=A()
- b=A()
- print(a.N) # 输出结果:0
- print(b.N) # 输出结果:0
- print(A.N) # 输出结果:0
- #此时a.N和b.N的值都为0,都是使用类的命名空间,调用了类的属性N=0
-
- b.N=100 # b创建了N=100,接下来b调用的N是 b实例的命名空间里的N
- print(a.N) # 输出结果:0
- print(b.N) # 输出结果:100
- print(A.N) # 输出结果:0
-
- a.xy() # a实例调用xy()方法,修改了类的命名空间里的属性N=123
- print(a.N) # 输出结果:123
- print(b.N) # 输出结果:100
- print(A.N) # 输出结果:123
- #此时a使用的是类的命名空间,a.N、A.N输出的都是123,b使用的是自己实例的命名空间
'运行
类定义了 _ _init_ _() 方法,类的实例化操作就会自动调用 _ _init_ _() 方法。_ _init_ _() 方法可以有参数,参数通过 _ _init_ _() 传递到类的实例化操作上。例如:
- class number_data:
- def __init__(self, int_data, float_data):
- self.i = int_data
- self.f = float_data
- x = number_data(3, 4.5)
- print(x.i, x.f) # 输出结果:3 4.5
'运行
- import dataclasses
- @dataclasses.dataclass
- class People:
- name: str = "" # 前提:必须为属性,声明类型,否则会报错
- age: int = 0
- sex: str = ""
- def show(self):
- print(self.name)
- print(self.age)
- print(self.sex)
- a = People("小石", 19, "男")
- a.show() # 输出结果:小石 19 男
'运行
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例该参数的名称是 self。
- #例1
- class myclass:
- def pr(self):
- print(self)
- print(self.__class__)
- t = myclass()
- t.pr()
- #输出
- '''
- <__main__.myclass object at 0x000002392516FFD0>
- <class '__main__.myclass'>
- '''
-
- #例2
- class people:
- # 定义基本属性
- name = ''
- age = 0
- # 定义构造方法
- def __init__(self, n, a, ):
- self.name = n
- self.age = a
- def speak(self):
- print("%s 说: 我 %d 岁。" % (self.name, self.age))
-
- # 实例化类
- p = people('小石', 18)
- p.speak() # 输出结果:小石 说: 我 18 岁。
- '''
- 在类的内部,使用def关键字来定义一个方法,与一般函数定义不同,
- 类方法必须包含参数self, 且为第一个参数,self 代表的是类的实例。
- '''
'运行
从上述例1执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。self 不是 python 关键字,我们把他换成别的字符串也是可以正常执行的:
- class myclass:
- def pr(me):
- print(me)
- print(me.__class__)
- t = myclass()
- t.pr()
- #输出
- '''
- <__main__.myclass object at 0x000002392516FFD0>
- <class '__main__.myclass'>
- '''
'运行
封装是把数据(属性或字段)和对这些数据的操作(方法)捆绑在一起,并隐藏内部实现细节的过程。通俗地讲,封装就像一个“黑箱”,我们只需要知道怎么使用这个箱子(调用接口),而不需要了解箱子内部是如何工作的。在Python中,类可以定义私有变量(通常通过双下划线前缀_ _var
来表示),外部无法直接访问,只能通过类提供的公共方法进行操作,确保对象中的数据安全。
举个生活中的例子来解释封装:
遥控器内部有很多复杂的电子元件和电路(这些是“数据”),但我们并不需要知道它们是如何工作的。我们只需要通过按钮(“方法”)来操作它,比如按下开关按钮、调节音量等。
- class RemoteController: #定义遥控器类
- # 内部复杂的数据结构(类似电子元件和电路)
- __battery_level = 100 # 私有属性,外部无法直接访问或修改
- def __init__(self):
- self.__turn_on_tv() # 初始化时打开电视
- # 对外提供的操作接口(方法)
- def turn_on(self):
- self.__operate_tv("打开电视机")
- def turn_off(self):
- self.__operate_tv("关闭电视机")
- def change_volume(self, volume):
- self.__set_volume(volume)
- # 内部实现细节(不直接暴露给外部)
- def __turn_on_tv(self):
- print("TV is turning on...")
- def __operate_tv(self, action):
- print(f"Operating TV: {action}")
- def __set_volume(self, volume):
- if 0 <= volume <= 100:
- print(f"Setting volume to {volume}")
- else:
- print("Invalid volume level!")
-
- # 使用者视角:
- remote = RemoteController()
- remote.turn_on() # 按下开关键
- 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装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。
生活例子:假设你有一本私人日记,你不想让所有人都看到,但允许家人在必要时查看。日记本就像是一个类,而日记内容是其中的一个变量 _content。虽然不鼓励外人直接翻看你的日记,但在家庭内部,父母或兄弟姐妹可以出于关心询问并阅读。
- class Diary:
- def __init__(self, content):
- self._content = content
- def share_with_family(self):
- return self._content # 家庭成员可以通过这个方法来读取日记内容
- daliy=Diary("今天天气真好!")
- print(daliy.share_with_family()) # 输出结果:今天天气真好!
- # 虽然按照约定不应该直接访问,但仍然可以这样做
- print(daliy._content) # 输出结果:今天天气真好!
'运行
生活例子:现在设想你有一个保险箱,它有一个非常私密的四位数密码 __password,除了你自己,任何人都不能知道或更改。即使是在保险箱内部,密码也不会被轻易公开,而是通过一种安全的方式来验证和修改密码。
- class Safe:
- def __init__(self, password):
- self.__password = password
- #定义验证修改密码方法
- def verify_and_change_password(self, old_password, new_password):
- if self.__password == old_password:
- self.__password = new_password
- print(f"密码修改成功,新密码为:{self.__password}")
- else:
- print("密码修改失败,旧密码输入有误.")
-
- obj=Safe("123456")
- print(obj.__password)
- '''直接访问会报错
- print(obj.__password)
- AttributeError: 'Safe' object has no attribute '__password'
- '''
- # 但实际上仍可通过原名访问,但这违反了封装原则
- print(obj._Safe__password) # 输出: 123456,但这并不是推荐的做法
-
- obj.verify_and_change_password("123456", "654321")#修改密码 输出 密码修改成功,新密码为:654321
- obj.verify_and_change_password("123456", "654321")#修改密码 输出 密码修改失败,旧密码输入有误.
- obj.verify_and_change_password("654321", "111111")#修改密码 输出 密码修改成功,新密码为:111111
-
- # 强行修改
- obj.__password = "123321"
- print(obj.__password) #强行修改后可以直接访问到属性值,
- #这里尝试直接修改__password属性时,
- #Python解释器会将__password改写为 _Safe__password(即在类名和原属性名间插入一个下划线),所以上述代码依然能够改变密码:
- #但是,直接修改私有变量违反了封装原则,应当避免这样做。在设计类时,采用getter和setter方法来控制对私有属性的访问是更好的做法。
1.修饰方法,使方法可以像属性一样访问。
2.与所定义的属性配合使用,这样可以防止属性被修改。
由于python进行属性的定义时,没办法设置私有属性,因此要通过@property的方法来进行设置。这样可以隐藏属性名,让用户进行使用的时候无法随意修改。
生活例子:想象一下你拥有一台智能冰箱,冰箱有一个显示屏显示当前内部温度。为了保证冰箱运行的安全和稳定,你不希望别人随意修改设定温度,所以将温度设置为只读属性,只能查看不能直接修改。
- class SmartFridge:
- def __init__(self, current_temperature):
- self.__current_temperature = current_temperature #初始化 当前温度 属性值
- @property
- def temperature(self): # 这是一个只读属性,用于获取当前温度
- return self.__current_temperature
- # 可以定义一个方法来调整温度,但不是直接修改temperature属性
- def set_temperature(self, new_temperature):
- if 0 <= new_temperature <= 10: # 假设合理温度范围为0-10度
- self.__current_temperature = new_temperature
- print(f"温度设置为{new_temperature}")
- else:
- print("温度设置无效")
-
- fridge = SmartFridge(3)
- print(fridge.temperature) # 输出:5
- fridge.set_temperature(6) # 正确做法是通过set_temperature方法调整温度
- print(fridge.temperature) # 输出:6
- fridge.set_temperature(11) # 输出:温度设置无效
-
- fridge.temperature = 5
- '''这将引发错误,因为我们已将其设置为只读属性
- fridge.temperature = 5
- AttributeError: can't set attribute 'temperature'
- '''
下划线和双下划线开头的命令方式,只是一种约定,没有强制作用。总结来说,遵循这些下划线约定有助于提高代码质量、可读性和维护性。虽然它们不是强制性的,但是为了遵循最佳实践并编写易于理解和协作的代码,建议在开发过程中遵循这些约定。
继承是面向对象编程中的一个核心概念,它允许创建一个新类(称为子类或派生类),该类可以从已存在的类(称为父类或基类)中获取属性和方法。通俗来说,继承就像是现实生活中的一种“模板”或者“蓝图”的应用。
生活例子: 假设我们有一个基础的交通工具类,其中包含了所有交通工具共有的属性(如颜色、速度等)和方法(如启动、停止、加速等)。现在,我们创建一个新的汽车类,一个新的自行车类,分别都是特殊的交通工具,以及创建一个功能类。代码展示继承、多继承、重写父类方法如下:
- #定义一个交通工具类
- class Transportation:
- def __init__(self, color):
- self.color = color
- def start(self):
- print("启动")
- def stop(self):
- print("停止")
- def accelerate(self):
- print("踩油门加速")
-
- class func:
- def func1(self, name):
- print(f"{name}载人出行")
-
- #单继承
- class Car(Transportation):
- def __init__(self, color, wheels):
- super().__init__(color) # 使用 super() 调用父类的 __init__ 方法
- self.wheels = wheels # 为 Car 类添加 车轮 属性
- def horn(self):
- print(f"{self.color}汽车{self.wheels}个轮子")
- print("汽车滴滴响")
-
- car=Car("红色", 4)
- car.start() #继承父类的方法,输出:启动
- car.stop() #继承父类的方法,输出:停止
- car.accelerate() #继承父类的方法,输出:踩油门加速
- car.horn() #继承父类的方法,输出:红色汽车4个轮子
-
- #多继承+重写父类方法
- class bicycle(Transportation,func):
- def __init__(self, color, wheels):
- super().__init__(color) # 使用 super() 调用父类的 __init__ 方法
- self.wheels = wheels # 为 bicycle 类添加 车轮 属性
- print(f"{self.color}自行车{self.wheels}个轮子")
- def accelerate(self):
- print("使劲踩踏加速")
-
- bic=bicycle("黑色", 2) #实例化类,输出:黑色自行车2个轮子
- bic.start() #继承父类的Transportation方法,输出:启动
- bic.stop() #继承父类Transportation的方法,输出:停止
- bic.func1("自行车") #继承父类func方法,输出:自行车载人出行
- bic.accelerate() #重写父类Transportation方法,输出:使劲踩踏加速
'运行
多态意味着同一个接口可以有不同的表现形式。在OOP中,这意味着子类可以覆盖或重写父类的方法,从而使得同一消息可以根据发送的对象类型产生不同的结果。同时,由于Python支持鸭子类型(Duck Typing),即“如果它走起路来像鸭子,叫起来也像鸭子,那它就是鸭子”,所以不同类型的对象只要具有相同的方法签名,就可以被同样的方式调用。
生活例子: 假设我们有一批电器设备,它们都有一个“开机”功能,但是具体怎么开机取决于设备的类型:
- 电视(TV):按下遥控器上的电源按钮,电视就会开启并显示画面。
- 电脑(computer):按一下主机上的开机键,电脑会启动并显示画面。
- 电灯(Light):旋动开关或者通过智能灯泡APP点击开灯按钮,电灯亮起。
在程序设计中,我们可以抽象出一个ElectricalDevice
基类,其中有一个通用的turn_on()
方法。然后定义TV、computer和Light三个子类,分别重写turn_on()
方法以实现各自设备的开机行为。当调用任意一个设备实例的turn_on()
方法时,执行的就是对应类型的开机操作。这就是多态的体现——通过统一的接口(方法名),实现了不同对象的不同表现形式。
- class TV(ElectricalDevice):
- def turn_on(self):
- print("按下遥控器上的电源按钮,电视就会开启并显示画面")
-
- class Light(ElectricalDevice):
- def turn_on(self):
- print("按下墙上的开关,灯就会亮")
-
- class computer(ElectricalDevice):
- def turn_on(self):
- print("按一下主机上的电源按钮,电脑就会开机")
-
- def turn_on(devices):
- devices.turn_on() # 传入的对象不同,turn_on()方法对应的操作和现象也不同
-
- turn_on(TV()) # 输出:按下遥控器上的电源按钮,电视就会开启并显示画面
- turn_on(Light()) # 输出:按下墙上的开关,灯就会亮
- turn_on(computer()) # 输出:按一下主机上的电源按钮,电脑就会开机
- 多态保证了代码的灵活性
- 忽略对象是实际类型,而是以胜任的方式充当任意类型,一个对象可以以不同的形态去呈现
- 多态是方法的多态,属性没有多态
- 多态的存在有 2 个必要条件:继承、方法重写
总结来说,面向对象编程的核心在于通过模拟现实世界的实体和关系,建立模型,并利用封装、继承和多态等机制组织代码,使程序更加清晰、模块化和易于维护。希望这篇文章能帮助友友们快速理解弄懂面向对象。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。