python学习笔记(面向对象)
一、什么是类
文章目录
- python学习笔记(面向对象)
- 一、什么是类
- 二、创建一个简单的类
- 三、类中的方法:动作
- 方法的种类:
- 1、普通方法
- 2、类方法和静态方法
- 3、魔术方法
- 四、私有化
- 五、继承
- has a类型
- is a类型
- 多继承的搜索顺序
- 六、多态
- 七、开发模式:单例模式
- 八、实例训练
python是一门面向对象的编程语言,那么什么是面向对象呢?我们来简单打个比方,整个现实社会就好比我们的一个程序,在现实社会中存在的,类似于人、动物、桥梁、建筑等具体事物,就像我们的对象。
世间万物皆对象。
每个对象都有各自的特征,也有很多相同的特征,比如黑种人和黄种人,他们在肤色上,长相上都大相径庭,但是他们都有手、脚、眼睛等公共的特征,这个时候,类的概念就出现了。
类是用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法。
在上面的定义中我们可以发现两个关键词,属性和方法。如何定义这两个关键字,我们同样来打个比方。
例如一个黄种人,他有两个眼睛,一张嘴巴,两条腿,长得还挺帅(就是头发有点儿少),这就是他的属性。
这个黄种人还会跑,会吃饭,还会敲代码,这就是他的方法。
通过定义类的方式来构建一个面向对象的程序有什么好处呢?下面给大家分享一个小故事。
说是三国时期,曹操在宴会上酣畅淋漓,诗兴大发,写下“我很高兴,歌以咏志”。大臣们纷纷叫好,命令小的赶紧将曹丞相的诗做成牌匾。第二天酒醒后,曹操看到牌匾,诶,这诗是我写的?怎么这么庸俗?赶紧给我撤了,重新给我做一个,我想想,就”幸甚致哉,歌以咏至“吧。属下赶紧去把牌匾扔了重新做了一个。
定义类就好比活字印刷,如果采取了活字印刷的手段,曹丞相的第二个牌匾只需要修改四个字即可,可以节约很大一部分工作量,对于我们的编程来说也是这样。
大概了解类过后,我们来尝试着创建一个类吧!
二、创建一个简单的类
class 类名:# 属性:# 动作:
这就是定义一个类的最基本的格式,下面我们来创建一个手机类吧:
手机的属性有颜色,品牌,大小等等属性,同时又有打电话,刷微博等方法;
class Phone:brand = 'HUAWEI' # 定义一个品牌属性colour = 'yellow' # 定义一个颜色属性size = 'big' # 定义一个大小属性def call(self): # 定义一个打电话的方法print('我可以打电话!')def weibo(self): # 定义一个刷微博的方法print('我可以刷微博!')
大家可以发现,定义属性与定义变量时有相似之处,而在定义方法时又和定义函数有相似的地方,但是在定义方法的时候,我们发现参数框中出现了一个self参数,这是一个什么东西呢?
self直译过来是”自己“的意思,在之前我们也讲到,类是用来描述具有相同的属性和方法的对象的集合,意味着我们在类中定义的每一个方法,都要具体到对象才会有实际的意义,python解释器也给了我们这样规范的语法。接下来,我们就会用我们创建的这个手机类来创建一个正儿八经的手机。
# 使用类来创建对象
P20 = Phone()
print(P20)brand = P20.brand
print(brand)
P20.call()-----------------------------------------------------------------------------------------
<__main__.Phone object at 0x000001A411D5CFD0>
HUAWEI
我可以打电话!
这样我们就创建了一个Phone这个类里面的P20对象,通过类来创建对象的格式类似于函数声明,通过.可以调用属性和方法。
如果这个时候我们发现手机品牌不是华为,想要修改一下怎么办呢?
按照我们之前的修改变量的方法,通常我们会这么做:
P20.brand = 'xiaomi'
print(P20.brand)-----------------------------------------------------------------------------------------
xiaomi
果然我们修改成功了,在这个时候大家也许会有个疑问,我修改了P20的brand,Phone里面的brand会不会跟着改变呢?
brand = Phone.brand
print(brand)
HUAWEI
大家估计对这个结果不会感到太意外,毕竟我们只是修改了P20里面的brand嘛。来深究一下,当我们在定义一个类的时候,python底层会给我们创建一个类的空间,通过类来创建对象的时候,又会给这个对象分配一个空间,在对象的这个空间中拥有类里面的所有属性和方法。两个空间互不影响,所以当我们在修改对象属性时自然也就不会影响到类空间里面的东西。
所以我们也可以在对象的空间中添加一些类里面没有的属性,具体大家可以自己动手操作一下。
在这里涉及到了两个新的概念,类属性和对象属性。其实在前文介绍中也有所提及,类属性是指这一个大类所共有的属性,而对象属性则是该对象所特有的,在之后的内容中,关于二者的区分会有更详细的解释。
对类有了基本的了解后,我们来研究一些更深层次的东西。
三、类中的方法:动作
在上面章节的讲述中,我们大概了解了”方法“这个概念,接下来我们将会更深入地研究一下”方法“的奥秘。
方法的种类:
大体可以分为以下四种:
- 普通方法
- 类方法
- 静态方法
- 魔术方法
1、普通方法
# 普通方法格式:
def 方法名(self,[参数,参数]):pass
在上一节的操作中我们在类里面创建了一个没有参数的方法,类中的方法同函数类似,同样可以在里面添加参数。
class Phone:brand = 'xiaomi'price = 4999type = 'mate 80'# Phone类里面的方法:calldef call(self): # self 自身print('---------------', self)print('正在访问通讯录:')for person in self.address_book:print(person.items())print('正在打电话....')print('留言:', self.note)phone1 = Phone()
phone1.note = '我是phone1的note'
phone1.address_book = [{'18982409867': '吴彦祖'}, {'18023300819': '刘德华'}]
print(phone1)
phone1.call()-------------------------------------------------------------------------------------------------------- <__main__.Phone object at 0x000002A4573FCFD0>
正在访问通讯录:
dict_items([('18982409867', '吴彦祖')])
dict_items([('18023300819', '刘德华')])
正在打电话....
留言: 我是phone1的note
我们在创建一个Phone这个类以后又创建了phone1这个对象,同时在phone1中添加了address_book和note两个对象属性,这时候通过phone1来使用call这个方法就可以正常调用了。
虽然我们在运行的时候不会报错,但是在pycharm里面会出现黄色的标记以示警告,说明我们这种书写方法是不规范的,相信大家大概明白为什么会出现这样的情况。
如同前文所说,对象空间与类空间是两个独立的存在,我们在对像空间里面添加属性并不会影响到类空间,当我们通过这个类空间再创建一个对象时,新的对象只会拥有公共的类属性而不会带有前一个对象的属性,在新的对象中使用call()这个方法则会因为找不到参数而报错。
为了避免这样的问题出现,我们需要提前引入一个魔术方法。
class Phone:# 魔术方法:__init__def __init__(self, brand, price):self.brand = brandself.price = price# 放在这里面的属性可以让对象动态添加def show(self):print('品牌:{},价格:{}'.format(self.brand, self.price))p = Phone('HUAWEI', '5000')
p.show()-----------------------------------------------------------------------------------------品牌:HUAWEI,价格:5000
添加了init后在创建对象时需要添加在init里面定义的属性来完整新的对象,这样我们就可以使我们的对象在具有一定束缚的条件下变得更加丰富。
在这里我们就可以从代码的角度来看类属性和对象属性的区别,简单来说,放在init里面的就是对象属性,放在init外面的就是类属性,简单粗暴。在对象属性的前面均需要添加一个self。
2、类方法和静态方法
类似于类属性和对象属性,类方法是属于类空间,在创建对象之前就已经存在。
class Dog:def __init__(self, nickname):self.nickname = nicknamedef run(self):print('{}在院子里跑来跑去!'.format(self.nickname))@classmethoddef test(cls):print('-------类方法')@staticmethoddef test1():print('-------静态方法')d = Dog('大黄')
d.run()
d.test()
d.test1()-----------------------------------------------------------------------------------------大黄在院子里跑来跑去!
-------类方法
-------静态方法
这次我们定义了一个Dog类,定义了属性nickname,定义了普通方法run(),同时定义了一个类方法test()。在上述代码中,可以发现类方法定义的基本格式,即在普通方法的基础上加上装饰器@classmethod,同时默认参数从self变成了cls,这也体现了类方法的特点。静态方法与类方法的区别在于装饰器的不同,且没有默认参数。在生成对象d之后,我们同样可以调用类方法和静态方法,对象在生成的时候会继承类里面的一切。
思考:类方法中能否使用对象属性?
总结一下:
类方法的特点:
- 定义需要依赖装饰器@classmethod
- 类方法中的参数不是一个对象,而是类
- 类方法中只可以使用类属性
- 类方法中不可使用普通方法
静态方法:很类似于静态方法
- 需要装饰器@staticmethod
- 静态方法无需传递参数
- 也只能访问类里面的属性和方法,对象的无法访问
- 加载时机同类方法
不同之处:
- 装饰器不同
- 类方法有参数,静态方法没有参数
相同:
- 只能访问类里面的属性和方法,对象的无法访问
- 通过类名调用访问
- 都可以在创建之前使用,因为是不依赖于对象
普通方法 与 两者区别
不同:
- 没有装饰器
- 普通方法永远要依赖对象的创建
- 只有创建了对象只有才有普通方法
类方法的作用:
在对象创建之前,如果需要完成一些动作(功能),我们可以通过类方法来完成。
通过上面的学习,下面的案例想必对读者来说已经没有太大的问题了吧。
class Cat:type = '猫'# 通过__init__初始化的特征def __init__(self, nickname, age, color):self.nickname = nicknameself.age = ageself.color = colordef eat(self, food):print('{}喜欢吃{}'.format(self.nickname, food))def catch_mouse(self, color, weight):print('{}抓了一只{}kg的{}大老鼠'.format(self.nickname, weight, color))def sleep(self, hour):if hour < 5:print('乖乖!继续睡觉吧!')else:print('赶快起床出去抓老鼠!')def show(self):print('猫的详细信息:')print(self.nickname, self.age, self.color)# 创建对象
cat1 = Cat('coco', '3', '粉色')# 通过对象调用方法
cat1.catch_mouse('黑色', '2')
cat1.sleep(8)
cat1.eat('小金鱼')
cat1.show()-----------------------------------------------------------------------------------------coco抓了一只2kg的黑色大老鼠
赶快起床出去抓老鼠!
coco喜欢吃小金鱼
猫的详细信息:
coco 3 粉色
3、魔术方法
在介绍普通方法的时候,我们已经开始使用init这个魔术方法了,除了这个魔术方法外,再来介绍几个比较常用的魔术方法。
# 魔术方法
# __init__ 初始化魔术方法
# 触发时机:初始化对象时触发# __new__ 实例化的魔术方法
# 触发时机:在实例化对时触发# __call__ 对象调用方法
# 触发时机:当对象当成函数来去调用的时候# __str__
# 触发时机:打印对象名 自动触发去调用__str__里面的内容class Person:def __init__(self, name, age):print('----------->init')self.name = nameself.age = agedef __new__(cls, *args, **kwargs):print('------------>new')return object.__new__(cls)# 申请内存开辟空间def __call__(self, name):print('-----------------call')print('执行对象得到的参数是:', name)def __str__(self):return '名字是:' + self.name + '年龄是:' + str(self.age)p = Person('吴彦祖', 45)
print(p)
p('jack')----------------------------------------------------------------------------------------------------->new
----------->init
名字是:吴彦祖年龄是:45
-----------------call
执行对象得到的参数是: jack
一般情况下,在定义类系统底层会给我们生成一个new的魔术方法,大多数情况下都不需要重写,但在读源码的时候我们常常会看到。
四、私有化
私有化是一个针对属性的概念,格式上表示为 __属性。
class Student:def __init__(self, name, age):self.__name = nameself.__age = ageself.__score = 59def __str__(self):return '姓名:{},年龄:{},分数:{}'.format(self.__name, self.__age, self.__score)s = Student('吴彦祖', '45')
print(s.__name)-----------------------------------------------------------------------------------------AttributeError: 'Student' object has no attribute '__name'
可以发现,将属性进行私有化以后,创建的对象不能直接通过属性名访问到属性里面的内容。
在很多时候,我们想要在通过类创建对象时给予一些属性上的约束,例如上述代码,我们希望年龄在0到100岁之间,这样的功能可以通过我们的私有化来进行实现。
class Student:def __init__(self, name, age):self.__name = nameself.__age = ageself.__score = 59# 定义公有set和get方法# set是为了赋值# 作用:对传进来的东西加了要求def setAge(self, age):if age > 0 and age <= 100:self.__age = ageelse:print('年龄不在规定范围内。')# get是为了返回私有化的值def getAge(self):return self.__agedef __str__(self):return "姓名:{},年龄:{},分数:{}".format(self.__name, self.__age, self.__score)wuyanzu = Student('wuyanzu', 18)
print(wuyanzu)
wuyanzu.setAge(150)
print(wuyanzu.getAge())-----------------------------------------------------------------------------------------姓名:wuyanzu,年龄:18,分数:59
年龄不在规定范围内。
18
通过getAge()方法我们也可以取到私有化过后的值。
大家可能会有些疑问,访问私有化的值像上述代码那样还是有些麻烦,有没有让私有化不那么私有的办法呢?
答案当然是yes!
class Student:# __age = 18 # 类属性def __init__(self, name, age):self.__name = nameself.__age = age@propertydef age(self):return self.__age@age.setterdef age(self, age):if age > 0 and age <= 120:self.__age = ageelse:print('年龄不在规定范围内。')def __str__(self):return "姓名:{},年龄:{}".format(self.__name, self.__age)s = Student('wuyanzu', 20)
print(s)
s.age = 130
print(s.age)-----------------------------------------------------------------------------------------姓名:wuyanzu,年龄:20
年龄不在规定范围内。
20
通过两个装饰器@property,@属性名.setter也同样可以完成之前的功能,同时也可以对私有话的属性进行修改。
五、继承
has a类型
所谓继承,言简意赅,在面向对象中指的是在一个类中使用了另外一种自定义的类型。
何为自定义类型?
在之前的学习中,我们了解到了很多系统类型,类似于str,int,float,list,dict,tuple,set。在写代码的过程中,我们也需要自己创建一些类型,每写的一个类即为一种自定义的类型,废话不多说,来个例子看看。
class Book:def __init__(self, bname, author, number):self.bname = bnameself.author = authorself.number = numberdef __str__(self):return self.bname + self.author + self.numberclass Computer:def __init__(self, brand, type, color):self.brand = brandself.type = typeself.color = colordef online(self):print('正在上网。')def __str__(self):return '品牌:{} 类型:{} 颜色:{}'.format(self.brand, self.type, self.color)class Student:def __init__(self, name, computer, book):self.name = nameself.computer = computerself.book = []self.book.append(book)def borrow_book(self, book):for book in self.book:if book1.bname == book.bname:print('已经借过此书。')else:# 将参数book添加到列表中self.book.append(book1)print('添加成功!')def show_book(self):for book in self.book:print(book.bname)def __str__(self):return self.name +' '+ str(self.computer) + str(self.book)# 创建对象
computer = Computer('mac', 'mac pro 2018', '深灰色')
book = Book('盗墓笔记', '南派三叔', 10)
stu = Student('小明', computer, book)
book1 = Book('鬼吹灯', '天下霸唱', 8)
print(stu)stu.borrow_book(book1)
stu.show_book()-----------------------------------------------------------------------------------------小明 品牌:mac 类型:mac pro 2018 颜色:深灰色[<__main__.Book object at 0x0000029DDD406390>]
添加成功!
盗墓笔记
鬼吹灯
在上面的代码中,我们创建了Book、Computer、Student三个类,在Student的创建中使用到了Computer和Book两个类。
is a类型
生物学领域常常界门纲目科属种这几个层级进行分类,在代码世界里同样可以产生这样的效果。在定义一个类的时候,将这个类划分到从属的父类中。
class Person:def __init__(self, name, age):self.name = nameself.age = agedef eat(self):print(self.name + '正在吃饭。')def run(self):print(self.name + '正在跑步。')class Student(Person):def __init__(self, name, age, clazz):# 如何去调用父类__init__super().__init__(name, age) # super()父类对象self.clazz = clazzdef study(self, course):print('{}正在学习{}。'.format(self.name, course))def eat(self, food):super().eat()print(self.name + '正在吃饭,喜欢吃' + food)class Employee(Person):def __init__(self, name, age, salary, manager):super().__init__(name, age)self.salary = salaryself.manager = managerclass Doctor(Person):def __init__(self, name, age, patient):super().__init__(name, age)self.patient = patients = Student('小明', '18', '软件工程')
s.run()
s.eat('青椒')
e = Employee('tom', '22', '8000', 'me')-----------------------------------------------------------------------------------------小明正在跑步。
小明正在吃饭。
小明正在吃饭,喜欢吃青椒
特点:
- 如果类中不定义__init__,调用父类 super class的__init__
- 如果类继承了父类也需要定义自己的__init__,就需要在当前类的__init__调用一下父类__init__
- 如何调用父类__init__:
super().init(参数)
super(类名,对象).init(参数) - 如果父类有eat(),子类也定义一个eat()方法,默认搜索原则:先找当前类,没有再找父类
s.eat()
override: 重写(覆盖)
父类提供的方法不能满足子类的需求,就需要在子类定义一个同名的方法,这种行为叫重写 - 子类的方法中也可以调用父类的方法
super.方法名
多继承的搜索顺序
多继承的搜索类型分为两类,经典类和新式类,python 3版本以后均使用新式类进行,下面介绍一下新式类的搜索原则——广度优先。
class P1:def foo(self):print('p1--->foo')def bar(self):print('p1--->bar')class P2:def foo(self):print('p2--->foo')class C1(P1, P2):passclass C2(P1, P2):def bar(self):print('c2--->bar')class D(C1, C2):passd = D()
d.foo()
d.bar()-----------------------------------------------------------------------------------------p1--->foo
c2--->bar
创建对象d以后,foo()现在D里面找,没有,再去C1和C2里面,同样没有,再去P1和P2里面找最后在P1里面找到了,调用P1的foo()。
六、多态
class Person:def __init__(self, name):self.name = namedef feed_pet(self, pet):# isinstance(obj,类) ————判断obj 是不是类的对象或者判断obj是不是该子类的对象if isinstance(pet, Pet): # 判断类型传进来的类是否是petprint('{}喜欢养{},昵称是{}'.format(self.name, pet.role, pet.nickname))else:print('不是宠物类型。')class Pet:role = 'Pet'def __init__(self, nickname, age):self.nickname = nicknameself.age = agedef show(self):print('昵称:{},年龄:{}'.format(self.nickname, self.age))class Cat(Pet):role = '猫'def catch_mouse(self):print('抓老鼠。')class Dog(Pet):role = '狗'def watch_house(self):print('看家狗。')class Tiger:def eat(self):print('太可怕了,会吃人。')# 创建对象
cat = Cat('coco', 2)
dog = Dog('大黄', 4)
person = Person('吴彦祖')
person.feed_pet(cat)-----------------------------------------------------------------------------------------吴彦祖喜欢养猫,昵称是coco
七、开发模式:单例模式
单例模式是一种常用的开发模式,是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。
class Singleton:# 私有化__instance = Nonename = 'jack'# 重写__new__def __new__(cls, *args, **kwargs):print('-------->__new__')if cls.__instance is None:print(1)cls.__instance = object.__new__(cls)return cls.__instanceelse:print(2)return cls.__instancedef show(self):print('show', Singleton.name)s = Singleton()
s1 = Singleton()print(s)
print(s1)
s.show()
八、实例训练
题目:
编写一个简单的工资管理程序,系统可以管理以下四类人:工人(worker)、销售员(saleman)、经理(manger)、销售经理(salemanger)
所有的员工都具有员工号、姓名、工资等属性,有设置姓名,获取姓名,获取员工号,计算工资等办法。
1)工人:工人具有工作小时数和时薪的属性,工资计算方法为工作小时时薪
2)销售员:具有销售额和提成比例的属性,工资计算方法为工作小时数时薪
3)经理:具有固定月薪属性,工资计算方法为固定月薪
4)销售经理:工资计算方法为销售额*提成比例+固定月薪
请根据以上要求设计合理的类,完成以下功能:
1)添加所有类型的人员
2)计算月薪
3)显示所有人的工资情况
示例(部分):
class Person:def __init__(self, num, name, salary):self.num = numself.name = nameself.salary = salarydef __str__(self):msg = '工号:{},姓名:{},本月工资{}'.format(self.num, self.name, self.salary)return msgdef getSalary(self):return self.salaryclass Worker(Person):def __init__(self, num, name, salary, hours, per_hour):super().__init__(num, name, salary)self.hours = hoursself.per_hour = per_hourdef getSalary(self):money = self.hours * self.per_hourself.salary += moneyreturn self.salaryclass Salesman(Person):def __init__(self, num, name, salary, salemoney, percent):super().__init__(num, name, salary)self.salemoney = salemoneyself.percent = percentdef getSalary(self):money = self.salemoney * self.percentself.salary += moneyreturn self.salary# 创建子类对象
w = Worker('001', 'king', 2000, 160, 100)
s = w.getSalary()
print(s)