【Python中的面向对象】
Python中的面向对象
文章目录
面向对象编程是最有效的编程方法之一,在C++和Java,JavaScript等编程语言中也都支持面向对象编程, 当然python也不例外。
类
类是创建对象的“模板”,编写类时,可以定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。
- 实例化 : 根据类来创建对象
创建类
python2.x
和python3.x
创建类的过程有些细微的不同, 以下主要是以python3.x
的语法为主
- 关键字 :
class
用于定义一个类 - 类名第一个字母通常要大写
__init__(self)
: 为构造函数, 第一个参数必须为self, 后面的参数是实例化类时传入的参数- 定义类的时候若无参数传入可以不用定义构造函数
class Car(): def __init__(self,name,color,brand): # 定义对象属性(也叫变量) self.name = name # 获取存储在形参name中的值,并将其存储到变量name 中,然后该变量被关联到当前创建的实例 self.color = color self.brand = brand def kind(self): #类方法中的定义的对象属性是可以在同一个类中的方法中使用, 但是必须得在类方法中已经初始化才能给另一个类方法中 self.level = "很牛逼的车" size = "large" # 这个才是类方法的变量 print(self.name+"的车类型为::"+self.type) def drive(self): print(self.name+"开着一辆"+self.color+self.type) def evaluate(self): print(self.level)
self
是一个指向实例本身的引用,让实例能够访问类中的属性和函数(方法)。- 每个与类相关联的函数(方法)调用都自动传递实参self
- 定义类函数(方法)时,必须要用
self
作为形参, 这也是普通方法与类方法的主要区别 - 类方法只需要传入一个
self
作为实参, 它就可以任意使用类中的属性和调用类中的方法
使用类
- 用类来创建对象 :
对象名 = 类名()
- 访问对象属性 :
对象名.属性
- 使用对象方法 :
对象名.方法()
# 使用Car类创建一个实例car1 = Car("张三","粉色","奔驰") # 创建一个对象# 可以指定传入的参数, 若没有指定, 则必须要按顺序传参car2 = Car(name = "李四",brand="宝马",color="黑色") # 这种传参无需按顺序print(car1.color) # 使用对象的属性car1.kind() # 调用对象的方法car1.drive() # 调用对象的方法
- 修改属性
- 可直接通过
对象.属性
进行赋值修改 - 也可通过定义类方法来将属性值传入方法中
- 通过调用方法来改变属性值
- 可直接通过
class Car(): def __init__(self,name,color,brand): self.name = name self.color = color self.brand = brand self.capacity = 5 # 座位数量, 给默认初始值 self.num = 0 self.introduce = "车的简介" def update_capacity(self,capacity): self.capacity = capacity def update_num(self): self.num += 1 def getNum(self): print("num:%d"%self.num) def getCapacity(self): print("capacity:%d"%self.capacity) def getIntroduce(self): print("introduce:%s"%self.introduce) def printGet(self): self.getNum() self.getCapacity() self.getIntroduce()xiaoming_car = Car("小明","绿色","本田")xiaoming_car.printGet()print("修改后的属性值")# 第一种: 直接修改属性值xiaoming_car.introduce = "这是个牛逼的车"# 第二种: 通过类方法进行传参xiaoming_car.update_capacity(3)# 第三种: 通过类方法进行修改xiaoming_car.update_num()xiaoming_car.printGet()
输出 :
num:0capacity:5introduce:车的简介修改后的属性值num:1capacity:3introduce:这是个牛逼的车
类的专有方法
专有方法 | 说明 |
---|---|
__init__ |
构造函数,在生成对象时调用 |
__del__ |
析构函数,释放对象时使用 |
__repr__ |
打印,转换 |
__setitem__ |
按照索引赋值 |
__getitem__ |
按照索引获取值 |
__len__ |
获得长度 |
__cmp__ |
比较运算 |
__call__ |
函数调用 |
__add__ |
加运算 |
__sub__ |
减运算 |
__mul__ |
乘运算 |
__truediv__ |
除运算 |
__mod__ |
求余运算 |
__pow__ |
乘方 |
继承
继承是在已有的类的基础上,创建的新类,而这个新类"传承了"已有类的属性和方法,还能在此基础上自定义属于新类的东西。被继承的类就是父类, 新类就是子类,子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。
class 类名(继承类)
, 即继承的父类写在定义类的后面的括号里- python是可以多继承的, 若有多个父类则用
,
隔开,
子类的构造方法
-
创建子类的实例时, Python 首先需要完成的任务是给父类的所有属性赋值。为此,子类的方法
__init__()
需要父类施以援手 -
super()
是特殊函数(方法),将父类和子类关联起来。父类也叫超类(superclass), 因此用super()
来将父类与子类的构造方法联系起来super().__init__()
, 该写法必须放在子类的构造函数里即__init__()
函数里
-
举个栗子
class Game(): # 默认参数要跟在非默认参数后面, 不然会报错 #比如: name 和 price = 0.00, name必须放前面, price带默认参数须放后面 def __init__(self,name,type="手游",price=0.00): self.name = name self.type = type self.price = priceclass Shop(): def action(self): # print("欢迎来到{}游戏商店!".format(self.name)) # self.name使用子类的属性, 但是因为父类没有声明该属性, 若直接实例化父类, 再调用这个方法会报错 print("欢迎来到游戏商店!") class Shooter(Game,Shop): # 继承多个父类 def __init__(self,name,size,type="手游",price=0.00): # 使用super()即把父类下的构造方法内的属性都放到子类中, 使得子类得以继承父类的属性 super().__init__(name,type,price) # 相当于下面的写法 # self.name = name # self.type = type # self.price = price self.size = size #添加的子类属性, 仅在子类中生效
重写父类的方法
- 对于父类的方法,如果它的方法对子类不适用,都可在子类中对其进行重写
- 重写就是用父类的方法名重新定义一遍, 当子类与父类的方法名相同时, 解释器会优先调用子类方法的内容,从而忽略父类方法的内容
class Game(): # 默认参数要跟在非默认参数后面, 不然会报错 #比如: name 和 price = 0.00, name必须放前面, price带默认参数须放后面 def __init__(self,name,type="手游",price=0.00): self.name = name self.type = type self.price = priceclass Shop(): def action(self): print("欢迎来到游戏商店!")class Shooter(Game,Shop): # 继承多个父类 def __init__(self,name,size,type="手游",price=0.00): # 使用super()即把父类下的构造方法内的属性都放到子类中, 使得子类得以继承父类的属性 super().__init__(name,type,price) self.size = size def action(self): # 重写父类Shop的方法 print("欢迎来到{}游戏商店!".format(self.name))
将实例用作属性
当给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。可以将大型类拆分成多个协同工作的小类,然后再将小类在一个类中进行实例化,并将实例用作属性。
- 在类内实现另一个类的实例化, 即将别的类的实例作为该类的一个属性
- 这样的好处是可以将大型类拆分成多个协同工作的小类, 可以将一个庞大的类变小(指在代码行数上), 方便后续的开发和整理
class Game(): # 默认参数要跟在非默认参数后面, 不然会报错 #比如: name 和 price = 0.00, name必须放前面, price带默认参数须放后面 def __init__(self,name,type="手游",price=0.00): self.name = name self.type = type self.price = price def getName(self): print("这是一款%s游戏"%self.name) def getCost(self): print("游戏的花费%d"%self.price) def setPrice(self,set_price): self.price = set_price def promotePrice(self,cost): self.price += cost def getType(self): print("这款游戏的类型是%s"%self.type)class Shop(): def action(self): # print("欢迎来到{}游戏商店!".format(self.name)) # self.name使用子类的属性, 但是因为父类没有声明该属性, 若直接实例化父类, 再调用这个方法会报错 print("欢迎来到游戏商店!")class Skill(): def __init__(self,arm,energy): self.arm = arm self.energy = energy def attack(self): print("武器为%s"%self.arm)class Shooter(Game,Shop): # 继承多个父类 def __init__(self,name,size,type="手游",price=0.00): # 使用super()即把父类下的构造方法内的属性都放到子类中, 使得子类得以继承父类的属性 super().__init__(name,type,price) # self.name = name # self.type = type # self.price = price self.size = size self.skill = Skill("m416",500) # 在类内实现另一个类的实例化, 即将别的类的实例作为该类的一个属性 def action(self): print("欢迎来到{}游戏商店!".format(self.name))# 类的实例化# 注意 : 若指定传入参数可以不用按顺序进行写, 但必须全部都是写成按指定名字的形参进行传# 像这样是错误的game1 = Shooter(size="100GB",绝地求生","端游")# 不传参的就按默认值取game1 = Shooter(size="100GB",name="绝地求生",type="端游")game2 = Shooter("和平精英","5GB","手游")game2.skill.attack()
导入类
当不断给类增加新功能时, 文件会逐渐开始变得庞大, 不利于维护和代码的工整,这与python最初的简洁理念相违背的,python就是为了简洁工整,而严格使用缩进来规范代码逻辑;因此python允许将类存储在模块中,然后可以在主程序中导入所需的模块,这样的好处是使代码尽可能简洁。
- 一个模块就是一个
py
文件 - 可以将一个类写进一个模块里
- 一个模块中可以有多个类, 因此模块的文件名不要求首字母大写
导入单个类
-
使用
import
关键字导入 -
若从某个模块中导入一个类, 则用
from 模块名 import 类名
-
as
关键字可以给模块/类/函数指定别名- 如果要导入的模块/类/函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的 别名 —— 模块/类/函数的另一个名称,类似于外号。
- 要指定这种特殊外号,只需要在导入它后面使用
as
关键字 :import 模块/类/函数名 as 别名
或者from 模块名 import 类/函数名 as 别名
-
比如 :
from supermarket import GetMessage as gm
即从supermarket
模块中导入GetMessage
类, 将该类类名改为gm
p.s. 从模块中导入特定的方法(函数)也是用import
关键字, 即from 模块名 import 函数名
从一个模块中导入多个类
虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类, 即一个模块中可以有任意数量的类.
-
可根据需要在程序文件中导入任意数量的类
-
导入方法和导入单个类一样, 用
import
关键字, 用,
隔开 -
比如 :
from supermarket import Commodity,GetMessage,Pay
导入三个类 -
导入模块中的所有类 :
from 模块名 import *
- 不建议用这种方法导入
- 当类名很多时, 你将不知道你的程序用了哪些类
- 有可能导入了一个与程序文件中其他东西同名的类,引发难以察觉的错误
- 不建议用这种方法导入
-
也可以导入整个模块, 直接用
import 模块名
-
需要从一个模块中导入很多类时,最好导入整个模块
-
使用
模块名.类名
的方式来访问类可以避免了导入模块中的每个类可能引发的名称冲突。这个与导入类的所有模块是有区别的, 即使是类名相同, 但是导入整个模块的方式是通过
模块名.类名
来访问类, 就不会产生命名冲突, 而导入所有类的方式就只能以类名的方式进行访问, 这样就容易产生冲突。举个例子: 假设有一个
fruit.py
文件, 有多个类, 其中想访问Apple类# 导入 fruit 整个模块import fruita = fruit.Apple()# 导入 fruit 模块中的所有类from fruit import *a = Apple()
-
在一个模块中导入另一个模块
有时候,需要将类分散到多个模块中,以免模块太大,或在同一个模块中存储不相关的类。
将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。
在这种情况下,可在前一个模块中导入必要的类。
- 当一个模块中的类有依赖的类在另一个模块中, 就需要从另一个模块中导入所依赖的类到这个模块中
- 最后再将该模块的类导入到主程序中
Tips :
-
类名应采用 驼峰命名法 ,即将类名中的每个单词的首字母都大写,而不使用下划线。实例名和模块名都采用小写格式,并在单词之间加上下划线。
-
可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。
-
需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的 import 语句,再添加一个空行,然后编写导入你自己编写的模块的 import 语句。在包含多条 import 语句的程序中,这种做法让人更容易明白程序使用的各个模块都来自何方。
举个大栗子:
模仿超市购物, 将商品添加到购物车然后进行结账。
supermarket.py
文件
# 商品类class Commodity(): def __init__(self,name,price=0.00): #定义类的属性, 商品名称, 商品价格, 商品折扣 self.goodsName = name self.price = price self.discount=1.00 def setGoodsName(self,name): self.goodsName = name print("已将该商品命名为:{}",format(self.goodsName)) def setPrice(self,price): self.price = price print("该商品的价格已设为:{:.2f}元".format(self.price)) def setDiscount(self,discount): self.discount = discount print("该商品的折扣为{:.2f}".format(self.discount))# 结账class Pay(): def __init__(self): self.count = 0.00 self.dic = {} # 存放商品的名的列表, key为商品名, num为数量 def add(self,name,count, num=1): # 添加商品 # self.dic.__setitem__(name,num) #设置字典的key和value if num < 1: return False count = float(count) self.dic.setdefault(name, num) #设置字典的key和value now_count = count*num self.count += now_count print("添加到购物车的商品名为:{} 添加的金额为:{:.2f}".format(name,now_count)) def sub(self,name,count,num=1): # 删除商品 if num < 1: return False now_count = count*num self.count -= now_count self.dic.pop(name) print("删除购物车的商品名为:{} 应减的金额为:{:.2f}".format(name, now_count)) def balance(self): # 结账 print("账单 : ") print("商品名\t数量\t") for items in self.dic.items(): print("{}\t{}\t".format(items[0],items[1])) print("需要支付的总金额为:{:.2f}元".format(self.count))
message.py
文件
from supermarket import Commodity# 当前模块中的类有依赖于supermarket模块中的Commodity类# 获取商品信息类class GetMessage(Commodity): def __init__(self,name,price=0.00): super().__init__(name,price) # 将父类的构造函数中的属性相互关联 def getName(self): try: # print("该商品的名称为:{}".format(self.goodsName)) return self.goodsName except: print("该商品并没有设置命名") def getPrice(self): try: # print("该商品的价格为{:.2f}元".format(self.price)) return self.price except: print("该商品还没有设置价格") def getDiscount(self): # if self.discount == 1.00: # print("该商品没有打折扣") # else: # print("该商品的折扣为{:.2f}".format(self.discount)) return self.discount
shopping.py
from message import GetMessage as gmfrom supermarket import Commodity, Pay# 添加商品信息def goods(): apple = gm("苹果", 2) orange = gm("橙子", 3) banana = gm("香蕉", 1.5) return [apple,orange,banana]def goodsMessage(ls): print("输出商品信息:") print("商品名称\t商品价格\t商品折扣\t") for item in ls: print("{}\t{}\t\t{}\t".format(item.getName(),item.getPrice(),item.getDiscount())) print("对应列表索引:") num = 0 for item in ls: print("{}\t{}".format(num,item.getName())) num+=1def buy(ls): sum = Pay() # sum.add(banana.goodsName, banana.price, 3) while True: a = input("请输入你要添加到购物车的索引:(输入q退出结账)") if a == 'q': break try: num = int(a) b = int(input("请输入要购买的数量:")) sum.add(ls[num].getName(), ls[num].getPrice(), b) except: print("输入有误,请重新输入") continue # 最后结账 sum.balance()if __name__ == '__main__': ls =goods() goodsMessage(ls) buy(ls)
tips : 从shopping.py文件开始运行, 这三个py文件要放在同个文件夹下, 这样系统才能顺利找到模块的路径
运行效果 :
输出商品信息:商品名称商品价格商品折扣苹果 21.0橙子 31.0香蕉 1.51.0对应列表索引:0苹果1橙子2香蕉请输入你要添加到购物车的索引:(输入q退出结账)0请输入要购买的数量:2添加到购物车的商品名为:苹果 添加的金额为:4.00请输入你要添加到购物车的索引:(输入q退出结账)1请输入要购买的数量:3添加到购物车的商品名为:橙子 添加的金额为:9.00请输入你要添加到购物车的索引:(输入q退出结账)q账单 : 商品名数量苹果 2橙子 3需要支付的总金额为:13.00元
参考资料 :
《Python 编程:从入门到实践》[ 美 ] Eric Matthes 译者:袁国忠
创作挑战赛 新人创作奖励来咯,坚持创作打卡瓜分现金大奖