一、面向对象
类:就是把相同的事物的动作和特征整合到一起
类是一个抽象的。
对象:基于类创建的一个具体的事物,
也是特征和动作整合一起的
#最初始的写法 people = { 'name': '奥巴马', 'gender': '男', 'age': '50', } def ment1(people): print('这个叫【%s】的人,正在跑步'%people['name']) def ment2(people): print('这个叫【%s】,今年【%s】岁的【%s】人,吃饭'%(people['name'], people['age'],people['gender'])) ment1(people) ment2(people)
#使用函数去编写面向对象。 def peo(): def ment1(people): print('这个叫【%s】的人,正在跑步'%people['name']) def ment2(people): print('这个叫【%s】,今年【%s】岁的【%s】人,吃饭'%(people['name'], people['age'],people['gender'])) people = { 'name': '奥巴马', 'gender': '男', 'age': '50', 'run':ment1, 'ect':ment2, } return people f = peo() f['run'](peo())
#使用函数去编写面向对象。优化版 # def peo(name,gender,age): # #要做的事: # def ment1(people): # print('这个叫【%s】的人,正在跑步'%people['name']) # def ment2(people): # print('这个叫【%s】,今年【%s】岁的【%s】人,吃饭'%(people['name'], # people['age'],people['gender'])) # #将定义的字典,写入函数中 # def init(name,gender,age): # people = { # 'name': name, # 'gender': gender, # 'age': age, # 'run': ment1, # 'ect': ment2, # } # return people #返回字典 # res = init(name,gender,age) #返回字典的这个函数 # return res # f = peo('奥巴马','男','20') #这个返回值是init(name,gender,age) # f1 = peo('奥巴马','男','20') # #f['run'](f):调用的是ment1这个函数,因为他有一个参数, # # 就是people,,这个字典,所以()中要跟一个参数就是peo('奥巴马','男','20')的返回值 # f['run'](f) # f['ect'](f1)
PS.
面向对象编程,和程序设计面向对象 ,是没有关系的
面向对象编程,是使用class类来进行编程的
程序设计面向对象:是使用def()来编程的
二、类/对象相关的知识
class类的创建方法
声明类的方式,类名的开头最好是大写字母
大前提: 1.只有在python2中才分新式类和经典类,python3中统一都是新式类 2.新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类 3.所有类甭管是否显式声明父类,都有一个默认继承object父类(讲继承时会讲,先记住) 在python2中的区分 经典类: class 类名: pass 经典类: class 类名(父类): pass 在python3中,上述两种定义方式全都是新式类
1、类的属性
dir(类名):查出的是类的一个名字列表
类名.__dict__:查看的是一个类的属性字典,
class student: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def jineng(self): print('%s 走路'%self.name) s1 = student('liu',23,'nan') s1.jineng()
重点:输入输出不要写到特定的函数当中,如果写到特定的函数中,那么这样的可读性就低了,
2、类的使用:
引用类的特征(类的变量)和技能(类的函数)
类:一:实例化,
二:引用名字(类名.变量名,类名.函数名)实例:引用名字(实例名.类的变量,实例名.绑定方法,实例名.实例自己的变量名)类.数据属性,调用的都是在自己的属性字典去调用的
class Student: def __init__(self,name,age): self.name = name self.age = age bart = Student('yang',20) print(bart.age)
3、实例化使用
实例属性只有数据属性,没有函数属性
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
class Student: def __init__(self,id): self.id = id s1 = Student('12345678890') print(s1.id)
实例化:类名+括号
class Student: def __init__(self,id): self.id = id s1 = Student('12345678890')
self的作用是在实例化时自动将对象/实例本身传给__init__的第一个参数,self可以是任意名字,但是瞎几把写别人就看不懂了。
3.1查看类里面的属性
一:我们定义的类的属性到底存到哪里了?有两种方式查看 dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 二:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
类名.__mro__#类的父级以及本身
数据封装
##将上面的代码,进行数据封装使用 def foo(std): print('%s : %s' %(std.name,std.age)) foo(bart)
4.对象/实例只有一种作用:属性引用
#对象/实例本身其实只有数据属性 >>> g1.nickname '草丛伦' >>> g1.aggressivity >>> g1.life_value ''' 查看实例属性 同样是dir和内置__dict__两种方式 特殊实例属性 __class__ __dict__ .... '''
对象/实例本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都不会一样
5.对象之间的交互
class Riven: camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus; def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54; self.nickname=nickname #为自己的锐雯起个别名; self.aggressivity=aggressivity #英雄都有自己的攻击力; self.life_value=life_value #英雄都有自己的生命值; def attack(self,enemy): #普通攻击技能,enemy是敌人; enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
实例出一个Riven来
>>> r1=Riven('锐雯雯')
交互:锐雯雯攻击草丛伦,反之一样
>>> g1.life_value 455 >>> r1.attack(g1) >>> g1.life_value 401
6.类名称空间与对象/实例名称空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:数据属性和函数属性
其中类的数据属性是共享给所有对象的
>>> id(r1.camp) #本质就是在引用类的camp属性,二者id一样 4315241024 >>> id(Riven.camp) 4315241024
而类的函数属性是绑定到所有对象的:
>>> id(r1.attack) 4302501512 >>> id(Riven.attack) 4315244200 ''' r1.attack就是在执行Riven.attack的功能,python的class机制会将Riven的函数属性attack绑定给r1,r1相当于拿到了一个指针,指向Riven类的attack功能 除此之外r1.attack()会将r1传给attack的第一个参数 '''
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
使用多个类做的交互:
class Riven: camp='Noxus' def __init__(self,nickname, aggressivity=54, life_value=414, money=1001, armor=3): self.nickname=nickname self.aggressivity=aggressivity self.life_value=life_value self.money=money self.armor=armor def attack(self,enemy): damage_value=self.aggressivity-enemy.armor enemy.life_value-=damage_value class Garen: camp='Demacia' def __init__(self,nickname, #名称 aggressivity=58, #初始攻击力 life_value=455, #生命值 money=100, #价格 armor=10): #护甲 self.nickname=nickname self.aggressivity=aggressivity self.life_value=life_value self.money=money self.armor=armor def attack(self,enemy): #enemy:敌人 '攻击力-护甲,生命值减攻击,得到新的生命值' damage_value=self.aggressivity-enemy.armor enemy.life_value-=damage_value class BlackCleaver: def __init__(self,price=475,aggrev=9,life_value=100): self.price=price #装备的金额 self.aggrev=aggrev #加9点攻击 self.life_value=life_value #生命 def update(self,obj): obj.money-=self.price #减钱 obj.aggressivity+=self.aggrev #加攻击 obj.life_value+=self.life_value #加生命值 def fire(self,obj): #这是该装备的主动技能,喷火,烧死对方 obj.life_value-=1000 #假设火烧的攻击力是1000 r1=Riven('草丛伦') g1=Garen('盖文') b1=BlackCleaver() print(r1.aggressivity,r1.life_value,r1.money) #r1的攻击力,生命值,护甲 if r1.money > b1.price: r1.b1=b1 b1.update(r1) print(r1.aggressivity,r1.life_value,r1.money) #r1的攻击力,生命值,护甲 print(g1.life_value) r1.attack(g1) #普通攻击 print(g1.life_value) r1.b1.fire(g1) #用装备攻击 print(g1.life_value) #g1的生命值小于0就死了
7.类的增删改查
class pro: sex = 'nan' sex1 = '男' def __init__(self,name,age): self.name = name self.age = age def tu_tan(self): print('随地吐痰') s = pro('yang',23) #增 s.qian = '20000' print(s.qian) #删 del s.qian print(s.__dict__) #改 s.sex = '11' print(s.sex) #查 print(s) #############从类外部的增删改查 #改,通过外部的函数,去更改类下面的函数 def food(self): print('正在吃东西') pro.tu_tan = food pro.tu_tan(s) #增 ,将类外部定义的函数,增加到类当中 def eat_food(self,food): print('正在吃%s'%food) pro.eat = eat_food pro.eat(s,'香蕉')
8.实例的增删改查
根据实例去改变的话,我在去用类.变量,不会改变我的元素
class pro: sex = 'nan' sex1 = '男' def __init__(self,name,age): self.name = name self.age = age def tu_tan(self): print('随地吐痰') s = pro('yang',23) #增 s.qian = '20000' print(s.qian) #删 del s.qian print(s.__dict__) #改 s.sex = '11' print(s.sex) #查 print(s)
9.类使用的方法需要注意的
类和实例化里面的属性,都是通过.的方式去查看__dict__里面的属性,没用通过.的方式去查看的那都不会调用类里面的属性,而是从全局作用域里面去找的
class Chinese:
country='China'
def __init__(self,name):
self.name=name
def play_ball(self,ball):
print('%s 正在打 %s' %(self.name,ball))
p1=Chinese('alex')
print(p1.country)
p1.country='日本' #通过实例去更改属性,用实例去打印的更改了,类.的打印没有更改
print('类的--->',Chinese.country)
print('实例的',p1.country)
country='中国'
class Chinese:
def __init__(self,name):
self.name=name
def play_ball(self,ball):
print('%s 正在打 %s' %(self.name,ball))
p1=Chinese('alex')
print(p1.country) #在类的外部定义一个变量,再通过类.变量打印,是报错的,提示在当前这个类里面没有country这个属性
动态读取:
country='中国'
class Chinese:
def __init__(self,name):
self.name=name
def play_ball(self,ball):
print('%s 正在打 %s' %(self.name,ball))
def shi_li_hua():
name=input('>>: ') #想动态添加,最好是在类的外部重新定义一个函数,去进行添加。直接在类的内部添加,可读性不高,别人会把你当傻逼
p1=Chinese(name)
# print(p1.country)
print(p1.name)
shi_li_hua()
只要是.的方式去调用的都是类或实例的引用,其他的都不是调用类里面的内容
country='中国-------------------' class Chinese: country='中国' def __init__(self,name): self.name=name print('--->',country) #没用调用类里面的内容 def play_ball(self,ball): print('%s 正在打 %s' %(self.name,ball)) print(Chinese.__dict__) #只要是.的方式不是类的调用,就是实例的调用, print(Chinese.country) p1=Chinese('alex') print('实例--------》',p1.country)
三、继承和派生
继承:就是将不同的类的相同性,放到父级类里面,在从父级类调用到子类里面,最大化的减少相同的代码。
例如:
class Hero: #父级类 def __init__(self, nickname,aggressivity,life_value): self.nickname = nickname #名字 self.aggressivity = aggressivity #攻击力 self.life_value = life_value #生命 def attack(self,enemy): #攻击 print('from Hero attack') enemy.life_value-=self.aggressivity class Garen(Hero): #子类调用分类 camp='Demacia' class Riven(Hero): #子类调用分类 camp='Noxus' g1 = Garen('yang',200,454) #子类写入自己的属性 r1 = Riven('r1',250,500) #子类写入自己的属性 print(g1.nickname) print(g1.aggressivity) print(g1.life_value) print(r1.nickname) print(r1.aggressivity) print(r1.life_value)
1.继承的重要性
==========================第一部分 例如 猫可以:喵喵叫、吃、喝、拉、撒 狗可以:汪汪叫、吃、喝、拉、撒 如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下: #猫和狗有大量相同的内容 class 猫: def 喵喵叫(self): print '喵喵叫' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something class 狗: def 汪汪叫(self): print '喵喵叫' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something ==========================第二部分 上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现: 动物:吃、喝、拉、撒 猫:喵喵叫(猫继承动物的功能) 狗:汪汪叫(狗继承动物的功能) 伪代码如下: class 动物: def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 猫(动物): def 喵喵叫(self): print '喵喵叫' # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 狗(动物): def 汪汪叫(self): print '喵喵叫' ==========================第三部分 #继承的代码实现 class Animal: def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '猫' def cry(self): print('喵喵叫') class Dog(Animal): def __init__(self, name): self.name = name self.breed='狗' def cry(self): print('汪汪叫') # ######### 执行 ######### c1 = Cat('小白家的小黑猫') c1.eat() c2 = Cat('小黑的小白猫') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat()
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
2、派生的用法
派生:就是在调用父级的内容之后,在继续使用父级的内容,还可以在子类里面添加自己的新功能,且还能保留父类的功能
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Hero: #父级类 def __init__(self, nickname,aggressivity,life_value): self.nickname = nickname #名字 self.aggressivity = aggressivity #攻击力 self.life_value = life_value #生命 def attack(self,enemy): #攻击 print('from Hero attack') enemy.life_value-=self.aggressivity class Garen(Hero): #子类调用分类 def __init__(self, nickname, aggressivity, life_value, script): Hero.__init__(self, nickname, aggressivity, life_value) self.script = script def attack(self, enemy): #在自己的子类中写入父类的这个方法,在添加新的功能 Hero.attack(self, enemy) #调用父类的功能 print('from Garen attack') class Riven(Hero): #子类调用分类 pass g1 = Garen('yang',200,454,12012) #子类写入自己的属性 r1 = Riven('r1',250,500) #子类写入自己的属性 print(g1.script) g1.attack(g1) ''' 在保留父级的功能,自己写的新功能也有 结果: from Hero attack from Garen attack '''
四、组合的重要性
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
''' 组合、继承 组合是:有效的;利用相同的资源, 例如:老师是教授“python"课程的,学生是学习”python“课程的 那么就可以写一个课程的类,来让双方调用。 ''' class Course: #课程的类 def __init__(self,course_name,course_price,course_time): self.course_name = course_name self.course_price = course_price self.course_time = course_time class Hero: #父类 def __init__(self,name,age,course): self.name = name self.age = age self.course = course #course = course_obj = Course('python',15800,'7m') ''' def kecheng(self): '这里就相当于被调用的子类都可以使用,但是是被写死的,学生就不能使用了,使用课程下的类的实例化属性,' print('%s正在教授【%s】课程....'%(self.name,self.course.course_name)) ''' class Teacher(Hero): teacher = '老师' def kecheng(self): '写在自己的类当中,写死了也没毛病,学生要用在自己去写去,使用课程下的类的实例化属性,' print('%s正在教授【%s】课程....' % (self.name, self.course.course_name)) class Student(Hero): student = '学生' def kecheng(self): print('%s正在学习【%s】课程....' % (self.name,self.course.course_name)) course_obj = Course('python',15800,'7m') #将课程的类实例化,在引入你需要的类当中 t1 = Teacher('engo',20,course_obj) # s1 = Student('engo',20,course_obj) t1.kecheng() #调用老师里面的课程的函数属性。 ''' 其实还可以在将课程的这个函数属性写入父类里面,在进行调用,如果在需要单独添加功能,再去自己类的函数中添加 如下: #父类的 def kecheng(self): print('这是父类写的功能) #子类的 def kecheng(self): print('%s正在学习【%s】课程....' % (self.name,self.course.course_name)) '''
5、接口与归一化设计
使用python模拟一个借口的效果
class Hero: def run(self): #模拟跑的接口 pass class People(Hero): def run(self): #人在跑 print('people running') class Dog(Hero): def run(self): #狗在跑 print('dog running') p1 = People() p1.run() d1 = Dog() d1.run() ''' 结果: people running dog running '''
强行让子类使用某个功能,添加抛出异常
class Animal: def run(self): raise AttributeError('子类必须实现这个方法') #抛出异常 def speak(self): raise AttributeError('子类必须实现这个方法')、
class People(Animal): def run(self): print('人正在走') def speak(self): print('说话') class Pig(Animal): def run(self): print('pig is walking') def speak(self): print('哼哼哼') peo1=People() # peo1.run() peo1.speak() #现在people这个类的speak函数是没有的,就在父类里面找,父类里面的speak函数是一个报错异常,就是让子类必须执行speak这个函数
六、抽象类
1 什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
2 为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
3. 在python中实现抽象类
import abc #使用abc模块进行强制抛出异常, #就是在父类的函数上加一个@abc.abstractmethod装饰器的功能,那么子类就必须要使用这个函数功能,否则就报错。 class Hero: @abc.abstractmethod def run(self): pass @abc.abstractmethod def speak(self): pass class Teacher(Hero): def run(self): print('run') def speak(self): print('speak') class Student(Hero): def run(self): print('run') def speak(self): print('speak') t1 = Teacher() s1 = Student() t1.run()
七、继承原理(子类的优先级)
在python3中是广度优先,看下面的实例
可以通过.__mro__的方式去查看绑定的父类
#python3是以广度优先的原理来进行继承的关系,广度优先是的分支不会一次性找到最后一个 # class A(object): # def test(self): # print('from A') # pass # class B(A): # # def test(self): # # print('from B') # pass # class C(A): # # def test(self): # # print('from C') # pass # class D(B): # # def test(self): # # print('from D') # pass # class E(C): # # def test(self): # # print('from E') # pass # # class F(D,E): # # def test(self): # # print('from F') # pass # f1=F() # f1.test() #f->d->b->e->c->a->a(object) ''' python3中是广度优先的分支不会一次性找到最后一个, 这是只有一个父类的时候。 到了最后一个就会找第二个分支的父类,一直找到最后一个也没找到的话, 就继续往下一个分支去找,一直找到最后一个分支都没有找到就找最后的那个头 '''
class x(object): def test(self): print('from X') class y(object): def test(self): print('from Y') class b(x): def test(self): print('from B') class c(y): def test(self): print('from C') class d(b): def test(self): print('from D') class e(c): def test(self): print('from E') class f(d,e): def test(self): print('from F') print(f.mro()) #f>>>d>>>b>>>x>>>e>>>c>>>y>>>(object) ''' python3中,当有两个父类的时候, 就不太一样了,先找第一条的路线,一直找到最后一个, 当最后一个也没有的话就找第二条的父类,直到找到为止。
图详解:
在python2中(深度优先)
在python2不能使用.__mro__的方式去查看
#python2中经典类的继承,在查找属性时遵循:深度优先 # class A: # # def test(self): # # print('from A') # pass # class B(A): # # def test(self): # # print('from B') # pass # class C(A): # # def test(self): # # print('from C') # pass # class D(B): # # def test(self): # # print('from D') # pass # # class E(C): # # def test(self): # # print('from E') # pass # class F(D,E): # # def test(self): # # print('from F') # pass # f1=F() # f1.test() # F->D->B->A->E->C ''' python2中经典类的继承,在查找属性时遵循:深度优先 在python2中是从一条道走到最后的父类,如果没有找到的话,在走第二条路线。这样找的。 '''
八、super()的使用
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
# #super在python2中的用法: # # 1:super(自己的类,self).父类的函数名字 # # 2:super只能用于新式类 # class People(object): # def __init__(self,name,sex,age): # self.name=name # self.age=age # self.sex=sex # def walk(self): # print('%s is walking' %self.name) # class Chinese(People): # country='China' # def __init__(self,name,sex,age,language='Chinese'): # # self.name=name # # self.sex=sex # # self.age=age # # People.__init__(self,name,sex,age) # super(Chinese,self).__init__(name,sex,age) # self.language=language # c=Chinese('egon','male',18) # print c.name,c.age,c.sex,c.language
#python3的super()用法 class people: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex class chinese(people): def __init__(self,name,age,sex,language): # super(people,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) super().__init__(name,age,sex) self.language = language print(chinese.mro()) #一个新的方法,查看父类。 c = chinese('yang','male',20,'chinese') print(c.name,c.age,c.sex,c.language) ''' 使用super调用的所有属性,都是从mro列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看mro列表 '''
九、多态与多态性
很多人喜欢将二者混为一谈,然后百思不得其解,其实只要分开看,就会很明朗
九.1多态
多态指的就是常使用的多态性,方法相同结果不同。
多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
#多态:同一种事物的多种形态动物分为人类、狗类等等.... class Hero: def run(self): print('这是父类定义的这个函数') class People(Hero): def run(self): print('人在跑') class Dog(Hero): def run(self): print('人在跑') class Big(Hero): def run(self): print('人在跑') ''' 同一种类的不同对象,调用的属性相同 '''
九.2多态性
多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
#定义类的阶段,多态性 class Hero: def run(self): print('这是父类定义的这个函数') class People(Hero): def run(self): print('人在跑') class Dog(Hero): def run(self): print('人在跑') class Big(Hero): def run(self): print('人在跑') p1 = People() p2 = Big() d1 = Dog() # p1.run() # p2.run() # d1.run() #多态性,一种调用方式,不同的执行效果 #这是相当定义一个接口,那么使用者就可以直接传参就可以使用了。。 def func(obj):#obj这个参数没有类型限制,可以传入不同类型的值 obj.run()#调用的逻辑都一样,执行的结果却不一样 ''' 同一个接口,传入的参数没有类型限制,实现不同的功能。。 多态性: 1.继承父类的共同特征和技能 2. 多态性:定义同意的接口,可以传入不同类型的值,但是调用的逻辑都一样,执行结果却不同, ''' func(p1) func(p2) func(d1)
十、封装
封装数据的主要原因是:保护隐私(作为男人的你,脸上就写着:我喜欢男人,你害怕么?)
封装方法的主要原因是:隔离复杂度(快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了,比如你不必知道你自己的尿是怎么流出来的,你直接掏出自己的接口就能用尿这个功能)
你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。还有你的头把你的脑子封装到了脑壳里,然后提供了眼睛这个接口....
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),就是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
class A: __x = 1 #_A__x = 1 变形 def __init__(self,name): self.name = name def __test(self): print('from a') a = A('yang') A.__x = 2 a.__x = 2 print(A.__dict__) print(a.__dict__) # print(A.__x) # print(a._A__x) print(A._A__x) print(a.__x) a._A__test() # A._A__test(1111) ''' 这是引用变量的方式 print(A._A__x):在类里面已经变形了,所以在打印的时候是类名._A__x的方式去打印出来 print(a.__x) :在类里面已经变形了,所以在打印的时候是对象._A__x的方式去打印出来 这是引用函数的方式 a._A__test() :在类里面已经变形了,所以在打印的时候是对象._A__x的方式去打印出来 A._A__test(1111) :也是一样的,在类里面就已经变形了,所以引用的方式是类名._A__x的方式 在外部增加新的变量, A.__x = 2 :这是通过类名.__x的方式去增加的,所以你打印的话也是这样去引用,并没有在类里面去定义, a.__x = 2 :这是通过对象.__x的方式去增加的,所以你打印的话也是这样去引用,这也是定义在实例化里面 ............................... 以上的方式都可以通过类.__dict__的方式去查看, 以上的方式都可以通过对象.__dict__的方式去查看, #__名字,这种语法,只在定义的时候才会有变形的效果,如果类或者对象已经产生了,就不会有变形的效果了 '''
封装的
class A: def fa(self): print('from a') def test(self): self.fa() class B(A): def fa(self): print('from b') pass b = B() # b.fa() b.test()#寻找的过程是b.test-->B-->A--->A.test()--->A. ''' b.test() 这个找的过程是。 b.test-->B-->A--->A.test-->b.fa() 如果B里面没有fa()这个函数,那么就在A里面去找了,A里面也没有那么久报错了 '''
在内部可以直接用__名字来访问到变形的属性
class A: def __init__(self): self.__x = 1 def tell(self): print(self.__x) #在内部可以直接用__名字来访问到变形的属性 a = A() # print(a.__dict__) A.tell(a)
class A: def __fa(self): print('from a') def test(self): self.__fa() class B(A): def __fa(self): print('from b') pass b = B() b.test() ''' #因为在定义的阶段已经变形了所以子类在引用父类的时候, # 调用的test()里面的__fa()就已经是_A__fa() '''
十一特性
1 #property优先级,是先找property下面的,在找类里面的 2 #@property(age):在函数上面加一个类似装饰器的函数,再次执行类里面的函数就可以使用对象.属性的方式去执行,不需要加()执行了 3 #@age.setter:可以进行动态修改已经被修装的属性对象, 4 # @age.deleter:绑定方法删除功能 5 6 #检测BMI指数 7 # class people: 8 # def __init__(self,name,width,height): 9 # self.name = name 10 # self.width = width 11 # self.height = height 12 # @property 13 # #在函数上面加一个类似装饰器的函数,再次执行类里面的函数就可以使用对象.属性的方式去执行,不需要加()执行了 14 # def bodyindex(self): 15 # return self.width/(self.height ** 2) 16 # 17 # p = people('yang',80,1.80) 18 # print(p.bodyindex) 19 20 21 22 class People: 23 def __init__(self,name,age): 24 self.name = name 25 # self.__age = age #这个就不会再初始化的时候执行函数属性的额方法 26 self.age = age #如果是这样的话,在初始化的时候,就会执行函数属性的方法,p.age = 222 27 28 @property 29 #在函数上面加一个类似装饰器的函数,再次执行类里面的函数就可以使用对象.属性的方式去执行,不需要加()执行了 30 def age(self): 31 return self.__age #p.__age = 222 32 33 ''' 34 1.这个是可以进行动态修改已经被修装的属性对象, 35 2.将已经有的函数进行一个装饰器的方法,这是固定的,将已有的函数进行更改,该函数下面有一个setter固定的方法 36 3.需要更改内容,是要添加一个参数, 37 4.我们要改什么属性,就将该属性 = 你的参数 38 ''' 39 @age.setter 40 def age(self,value): 41 if not isinstance(value,int): #判断如果不是你指定的类型, 42 raise TypeError('必须是整数型') #进行一个主动抛出异常的用法 43 self.__age = value #需要修改的类型,是这个已经变形的对象 p.__age = 222 44 45 @age.deleter 46 def age(self): 47 del self.__age #del p.__age 48 49 #在初始化的时候,__对象的方式是不会被修改的,但是.对象的方式就会修改,是将一个函数的以属性的方式给的 50 p = People('yang',222) 51 # print(p.age) 52 # p.age = 222 53 print(p.age) 54 del p.age 55 print(People.__dict__)
十二、静态方法和类方法
有三种方法:
- 1.被对象绑定的方法
- 2.不被绑定的方法staticmethod
- 3.新的实例化绑定classmethod
1.静态方法
初识staticmethod
class Foo: def spam(x,y,z): #类里面的一个函数,并且x,y,z都是参数和self都是一样的 print(x,y,z) spam=staticmethod(spam)#把spam函数做成静态方法 l = Foo() l.spam(1,2,3)
将staticmethod设置成装饰器
1 class Foo: 2 @staticmethod #spam = staticmethod(spam) 使用这个装饰器,那么当前的这个函数就适用于这个类 3 def spam(x,y,z): 4 print(x,y,z) 5 print(type(Foo.spam)) #他的类型就是一个函数, 6 Foo.spam(1,2,3) #使用类.函数属性(加上你的参数) 7 f = Foo() 8 f.spam(1,2,3) #使用实例对象.函数属性(加上你的参数) 9 ''' 10 <class 'function'> 11 1 2 3 12 1 2 3 13 '''
应用场景:编写类时需要采用很多不同的方式来创建实例,而我们只有一个__init__函数,此时静态方法就派上用场了
1 import time 2 class Date: 3 def __init__(self,year,month,day): 4 self.year = year 5 self.month = month 6 self.day = day 7 @staticmethod #如果不加staticmethod方法类可以调用,实例就不能调用了 8 def today():#用Date.today()的方式去创建实例,该实例用的是当前的时间 9 #获取结构化的时间{time.struct_time(tm_year=2017, tm_mon=4, tm_mday=22, tm_hour=16, tm_min=27, tm_sec=46, tm_wday=5, tm_yday=112, tm_isdst=0)} 10 t = time.localtime() 11 # print(t) 12 obj = Date(t.tm_year,t.tm_mon,t.tm_mday)#直接在内部进行了实例化,并且给一个返回值 13 return obj 14 @staticmethod 15 def tomorrow(): 16 t = time.localtime(time.time()+86400) 17 # print(t) 18 obj = Date(t.tm_year,t.tm_mon,t.tm_mday)#直接在内部进行了实例化,并且给一个返回值 19 return obj 20 21 #但凡是定义在类的内部,并且没有被任何装饰器修饰过的方法,都是绑定方法:有自动传值功能 22 a = Date(2017,2,4)#定义自己的时间 23 a1 = a.today() #绑定的方法也是有返回值的。在进行调用 24 print(a1.year,a1.day) 25 print(Date.today()) 26 27 #但凡是定义在类的内部,并且被staticmethod装饰器修饰过的方法,都是解除绑定的方法,实际上就函数:就没有自动传值功能了 28 b = Date.today()#采用当前的时间 29 c = Date.tomorrow() 30 print(b.day,b.month,b.year) 31 print(c.day,c.month,c.year)
2.类方法
@classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。
class Foo: def bar(self):#这个属于绑定方法,直接.的方式就能使用 pass # 把一个方法绑定给类,类.绑定到类的方法(), # 会把类本身当做第一个参数自动给绑定到类的方法 @classmethod def test(cls,x): print(cls,x)#拿掉一个类的内存地址后,就可实例化或者引用类的属性了 f = Foo() Foo.test(1)# f.test(1)#也是直接将对象传给第一个值,
classmethod的使用场景
import time class Date: def __init__(self,year,month,day): self.year = year self.month = month self.day = day @classmethod #产生新的实例去使用 def now(cls):#用Date.now()的形式去产生实例,该实例用的是当前时间 print(cls) t = time.localtime()#获取结构化的时间格式 obj = cls(t.tm_year,t.tm_mon,t.tm_mday)#新建实力并且返回 return obj @classmethod def tomorrow(cls):#用Date.tomorrow()的形式去产生实例,该实例用的是当前时间 print(cls) t = time.localtime(time.time()+86400) obj = cls(t.tm_year, t.tm_mon, t.tm_mday) return obj class Eurodate(Date): def __str__(self): return '年:%s,月:%s,日:%s'%(self.year,self.month,self.day) # f = Eurodate(2017,1,1) f = Eurodate.now() print(f)
在类内部定义的函数无非三种用途
一:绑定到对象的方法 只要是在类内部定义的,并且没有被任何装饰器修饰过的方法,都是绑定到对象的 class Foo: def test(self): #绑定到对象的方法 pass def test1(): #也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数所以会抛出异常 pass 绑定到对象,指的是:就给对象去用, 使用方式:对象.对象的绑定方法(),不用为self传值 特性:调用时会把对象本身当做第一个参数传给对象的绑定方法 二:绑定到类的方法:classmethod 在类内部定义的,并且被装饰器@classmethod修饰过的方法,都是绑定到类的 class Foo: def test(self): #绑定到对象的方法 pass def test1(): #也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数所以会抛出异常 pass 绑定到对象,指的是:就给对象去用, 使用方式:对象.对象的绑定方法() 特性:调用时会把对象本身当做第一个参数传给对象的绑定方法 三:解除绑定的方法:staticmethod 既不与类绑定,也不与对象绑定,不与任何事物绑定 绑定的特性:自动传值(绑定到类的就是自动传类,绑定到对象的就自动传对象) 解除绑定的特性:不管是类还是对象来调用,都没有自动传值这么一说了 所以说staticmethod就是相当于一个普通的工具包 class Foo: def test1(self): pass def test2(): pass @classmethod def test3(cls): pass @classmethod def test4(): pass @staticmethod def test5(): pass test1与test2都是绑定到对象方法:调用时就是操作对象本身 <function Foo.test1 at 0x0000000000D8E488> <function Foo.test2 at 0x0000000000D8E510> test3与test4都是绑定到类的方法:调用时就是操作类本身 <bound method Foo.test3 of <class '__main__.Foo'>> <bound method Foo.test4 of <class '__main__.Foo'>> test5是不与任何事物绑定的:就是一个工具包,谁来都可以用,没说专门操作谁这么一说 <function Foo.test5 at 0x0000000000D8E6A8>
小白容易犯的错误
1.面向对象的程序设计看起来高大上,所以我在编程时就应该保证通篇class,这样写出的程序一定是好的程序(面向对象只适合那些可扩展性要求比较高的场景)
2.很多人喜欢说面向对象三大特性(这是从哪传出来的,封装,多态,继承?漏洞太多太多,好吧暂且称为三大特性),那么我在基于面向对象编程时,我一定要让我定义的类中完整的包含这三种特性,这样写肯定是好的程序
好家伙,我说降龙十八掌有十八掌,那么你每次跟人干仗都要从第一掌打到第18掌这才显得你会了是么,我来一万个人你需要打10000*18掌对么,傻叉
3.类有类属性,实例有实例属性,所以我们在定义class时一定要定义出那么几个类属性,想不到怎么办,那就使劲的想,定义的越多越牛逼
这就犯了一个严重的错误,程序越早面向对象,死的越早,为啥面向对象,因为我们要将数据与功能结合到一起,程序整体的结构都没有出来,或者说需要考虑的问题你都没有搞清楚个八九不离十,你就开始面向对象了,这就导致了,你在那里干想,自以为想通了,定义了一堆属性,结果后来又都用不到,或者想不通到底应该定义啥,那就一直想吧,想着想着就疯了。
你见过哪家公司要开发一个软件,上来就开始写,肯定是频繁的开会讨论计划,请看第八节
4.既然这么麻烦,那么我彻底解脱了,我们不要用面向对象编程了,你啊,你有大才,你能成事啊,傻叉。
python中关于OOP的常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__