面向对象的三大特性 : 继承 多态 封装
# 继承 :
# 单继承 : ****
# 父类(超类、基类)
# 子类(派生类):派生方法和派生属性
# 子类的对象在调用方法和属性 : 先用自己的 自己没有 才用父类的
# 多继承 :(面试)
# 不会超过三个父类,不要超过三层 ***
# 如果子类自己有用自己的,如果没有就用离子类最近的那个父类的方法
# 抽象类和接口类 **
# 经典类和新式类 继承规则不同 深度优先和广度优先 ***** (面试)
# super 只能在python3中使用 mro ****
#super是根据mro广度优先顺序找上一个类
# 多态 : (面试)
在面向对象技术中,多态性是指
A.一个对象可同时指向不同类的对象
B.同一消息在不同时刻可调用不同方法
C.对象的生存周期中包含对象的不同状态
D.不同消息可以调用相同的方法
# 多态和鸭子类型
1.从方法的角度来看
·方法的重载和重写
2.从对象的角度来看
·对象的多态性主要分为两种:
(1)向上转型:子类对象--->父类对象(程序会自动完成)
格式:父类 父类对象=子类实
或者 父类 父类对象=new 子类
***向上转型后,因为操作的是父类对象,所以无法找
到在子类中定义的新方法;但如果子类重写了父类的
某个方法,则调用的是重写后的方法
# 封装 : *** (面试)
# 私有的
# __名字
# 只能在类的内部调用 子类都无法继承
人狗大战
def Person(life_value,aggr,name,job): def attack(dog): #定义人攻击技能 print('%s攻击了%s' % (person['name'], dog['name'])) dog['life_value'] = dog['life_value'] - person['aggr'] person = { #模子 'life_value':life_value,#生命值 'aggr':aggr, #aggressive攻击力 'name':name, 'job' : job, 'attack' : attack } return person boss_gold = Person(1000,2,'金老板','boss') #具体的对象 alex = Person(250,250,'alex','big_boss') #具体的对象 def Dog(name,life_value,aggr,kind): def bite(person): #定义狗的技能 print('%s咬了%s' % (dog['name'], person['name'])) person['life_value'] -= dog['aggr'] dog ={ 'name':name, 'life_value':life_value, 'aggr' : aggr, 'kind' : kind, 'bite' : bite } return dog alph = Dog('alphgou',10000,999,'AI') #定义具体的狗对象 flower = Dog('小花',150,300,'2ha') #定义具体的狗对象 alph['bite'](boss_gold) #狗调用技能咬人 print("%s剩余血量:%d"%(boss_gold["name"],boss_gold['life_value'])) alex['attack'](flower) #人调用技能攻击狗 print("%s剩余血量:%d"%(flower["name"],flower['life_value']))
输出结果:
alphgou咬了金老板
金老板剩余血量:1
alex攻击了小花
小花剩余血量:-100
上面你用的这种编程思想其实就是简单的面向对象编程,你的每一个决定可能都影响着你这场游戏的输赢。这也是不确定的。和我们之前写代码按部就班的走,最终都会实现我们要完成的事情不太一样了。
面向过程和面向对象
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计考虑周全什么时候处理什么东西。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。特征和技能分别对应对象的属性和方法。
面向对象的程序设计
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计。
在python 中面向对象的程序设计并不是全部。
面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
了解一些名词:类、对象、实例、实例化
类:具有相同特征的一类事物
对象/实例:具体的某一个事物
实例化:类——>对象的过程
类和对象
python中一切皆为对象,类型的本质就是类
比如dict类型就是dict类,这一类具有相同的属性和方法
{'k1':'v1'}是字典dict具体的某一个,这个就是对象
1,类的声明
class Person: #类名有两个作用 country = '中国' #静态属性、数据属性 def walk(self): #动态属性、函数属性、方法 print('walk')
2,类能完成的两个功能
类的属性引用,增删改查
print(Person.country) #静态属性的查看 print(Person.walk) Person.role = '人' #添加一个新静态属性 Person.country = '印度' #修改一个新静态属性 del Person.role #删除一个静态属性 print(Person.__dict__) print(Person.walk()) #报错 少self参数 Person.__dict__['country'] = '印度' #报错 不能改变 print(Person.__dict__['country']) print(Person.country)
实例化对象
实例化:从一个类中创造一个具体的对象的过程
class Person: country = '中国' def __init__(self,life_value,aggr,name,job): self.lv = life_value self.aggr = aggr self.name = name self.job = job def walk(self): print('%s is walking'%self.name) p = Person(1000,2,'金老板','boss') #如何让对象拥有具体的属性 p.walk() # Person.walk(p) print(p.lv) #Person的对象 print(p.aggr) print(p.name) print(p.job) p.sex = 'lady_boy' #添加一个属性 print(p.sex) print(p.__dict__) p.sex = 'girl' #属性的修改 print(p.sex) #属性的删除 del p.sex print(p.__dict__)
print(p.__dict__['lv']) #查看 p.__dict__['lv'] = 998 #对象名.__dict__中的属性值可以被修改 print(p.__dict__['lv']) p.__dict__['sex'] = 'lady_boy' #对象名.__dict__中的属性值可以增加 print(p.__dict__) p.__dict__.pop('sex') #对象名.__dict__中的属性值可以被删除 print(p.__dict__)
实例化对象
语法:对象名 = 类名(参数)
egg = Person('egon') #类名()就等于在执行Person.__init__() #执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。 #你可以偷偷的理解:egg = {'name':'egon','walk':walk}
查看属性和调用方法
print(egg.name) #查看属性 对象名.属性名 print(egg.walk()) #调用方法 对象名.方法名()
关于self
self:在实例化时自动将对象/实例本身传给__init__的第一个参数
查看定义的类的属性存在哪里
dir(类名)查出的是一个名字列表
类名.__dict__查出的是一个字典,key为属性名,value为属性值
小结:
类
类的定义 class关键字 类名的首字母大写
类中直接定义属性:静态属性(变量) 和 动态属性(函数)
类名的两个作用:属性的引用,实例化对象
类名和属性的操作:对象名.属性名 对象名.__dict__['属性名'](不能修改)
实例化:类名(参数)
构造方法:造对象
初始化__init__:给这个赤裸裸的对象添加一些属性 __init__
返回了一个初始化之后的对象
__init__方法:初始化方法,可以没有。给对象添加一些必要的基础属性
self的含义:就是这个对象本身
对象和属性的操作:对象名.属性名 对象名.__dict__['属性名']
class Person: country = '中国' def __init__(self,life_value,aggr,name,job): #self = {'lv':life_value} self.lv = life_value #属性,对象属性 self.aggr = aggr self.name = name self.job = job def walk(self): #动态属性、函数属性、方法 print('%s is walking'%self.name) boss_gold = Person(10,1,'太白太黑太亮','old_driver') alex = Person(100,2.5,'张嘴闭嘴tesla','鸡汤哥') boss_gold.walk() alex.walk() boss_gold.sex = 'None' print(boss_gold.__dict__) print(alex.__dict__)
输出结果
太白太黑太亮 is walking
张嘴闭嘴tesla is walking
{'lv': 10, 'aggr': 1, 'name': '太白太黑太亮', 'job': 'old_driver', 'sex': 'None'}
{'lv': 100, 'aggr': 2.5, 'name': '张嘴闭嘴tesla', 'job': '鸡汤哥'}
面向对象的交互示例(人狗大战)
#人类 : #属性 :life_value,aggr,name,job #方法: attack class Person: def __init__(self,life_value,aggr,name,job): self.life = life_value self.aggressive = aggr self.name = name self.job = job def attack(self,dog_obj): #boss_gold,tiedan print('%s 攻击了 %s'%(self.name,dog_obj.name)) dog_obj.life = dog_obj.life - self.aggressive #狗类: #属性:life_value,aggr,name,kind #方法:bite class Dog: def __init__(self,life_value,aggr,name,kind): self.life = life_value self.aggressive = aggr self.name = name self.kind = kind def bite(self,person_obj): print('%s 咬了 %s' % (self.name, person_obj.name)) person_obj.life -= self.aggressive tiedan = Dog(1000,100,'铁蛋','土狗') boss_gold = Person(100,2.5,'太黑','old_driver') boss_gold.attack(tiedan) #Person.attack(boss_gold,tiedan) print("%s剩余血量:%d"%(tiedan.name,tiedan.life)) tiedan.bite(boss_gold) print("%s剩余血量:%d"%(boss_gold.name,boss_gold.life))
结果
太黑 攻击了 铁蛋
铁蛋剩余血量:997
铁蛋 咬了 太黑
太黑剩余血量:0
类命名空间和对象,实例的命名空间
属性:静态属性 (直接和类名关联或者直接定义在class下的变量)
对象属性 (在类内和self关联,在类外和对象名关联的变量)
动态属性(函数)
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
其中类的数据属性是共享给所有对象的,而类的动态属性是绑定到所有对象的
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类,最后都找不到就抛出异常
class Foo: country = 'China' country_lst = ['China'] def __init__(self,name): self.name = name alex = Foo('alexander') egg = Foo('egon') alex.age = 90 alex.country_lst = [] #对象的命名空间创建 alex.country_lst.append('印度') print(Foo.country_lst) #["China"] Foo.role = 'Person' print(Foo.country) #China print(alex.name) #alexander print(egg.name) #egon # Foo.country alex.country = '印度' print(alex.country) #印度 print(egg.country) #China print(Foo.country) #China del alex.country print(alex.country) #印度 print(alex.country_lst) #["印度"] alex.country_lst.append('中国') print(alex.country_lst) #["印度","中国"] print(egg.country_lst) #["China"] print(Foo.country_lst) #["China"]
类名操作变量:不管操作可变还是不可变数据类型 都是类中对应的变量发生变化
对象名操作静态变量:
一,引用变量:先在自己的命名空间中查找,找不到就去类的命名空间找
二,修改变量:
1,如果是对可变数据类型中的元素进行修改,那么全局生效
2,如果是对变量进行重新赋值,那么只是在对象自己的命名空间里增加了一个新的属性
结论:应该尽量用类名去使用静态变量
对象使用名字的顺序:先用自己的,再用类的,对象可以使用类的,而类无法使用对象的。
***设计一个类,统计这个类被实例化的次数,且所有的对象共享这个属性
class Foo: count = 0 def __init__(self): Foo.count += 1 f = Foo() print(f.count) #1 f2 = Foo() print(f2.count) #2 f3 = Foo() print(f.count) #3 print(f2.count) #3 print(f3.count) #3
面向对象的组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合:什么有什么的关系,一个对象的属性是另外一个类的对象
#老师有生日 : 年月日 #将一个类的对象拥有的属性定义成一个类以提高代码的复用 class Teacher: def __init__(self,name,age,sex,year,month,day): self.name = name self.age = age self.sex = sex self.birth = Birthday(year,month,day) class Birthday: def __init__(self,year,month,day): self.year = year self.month = month self.day = day boss_gold = Teacher('太亮',40,'不详',1968,12,31) print(boss_gold.birth)
用组合写出一个环形的面积和周长
#圆形类 from math import pi class Circle: def __init__(self,r): self.radius = r def perimeter(self): return 2*pi*self.radius def area(self): return pi*self.radius**2 #环形类 class Ring: def __init__(self,outer_r,inner_r): self.outer_circle = Circle(outer_r) self.inner_circle = Circle(inner_r) def perimeter(self): return self.outer_circle.perimeter()+self.inner_circle.perimeter() def area(self): return self.outer_circle.area()-self.inner_circle.area() ring = Ring(20,10) print(ring.area()) print(ring.perimeter())
人狗大战(人加武器)
class Dog: # 定义一个狗类 def __init__(self, name, breed, aggressivity, life_value): self.name = name # 每一只狗都有自己的昵称; self.breed = breed # 每一只狗都有自己的品种; self.aggressivity = aggressivity # 每一只狗都有自己的攻击力; self.life_value = life_value # 每一只狗都有自己的生命值; def bite(self,people): people.life_value -= self.aggressivity
class Person: # 定义一个人类 def __init__(self, name, aggressivity, life_value, money): self.name = name # 每一个角色都有自己的昵称; self.aggressivity = aggressivity # 每一个角色都有自己的攻击力; self.life_value = life_value # 每一个角色都有自己的生命值; self.money = money def attack(self,dog): dog.life_value -= self.aggressivity def get_weapon(self,weapon_obj): if self.money > weapon_obj.price: self.money -= weapon_obj.price # 金老板花钱买武器 self.weapon = weapon_obj # 金老板装备打狗棒 self.aggressivity += weapon_obj.aggr # 金老板的攻击力增加了 #人 有 武器 —— 组合 #武器:攻击力,名字,价格 class Weapon: def __init__(self,name,price,aggr): self.name = name self.price = price self.aggr = aggr dgb = Weapon('打狗棒',99.8,100) boss_gold = Person('金老板',5,250,100) huang = Dog('大黄','藏獒',100,3000) boss_gold.get_weapon(dgb) boss_gold.attack(huang) print(huang.life_value)
面向对象的三大特性之一--------继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
继承 :至少两个类,什么是什么的关系,为了避免几个类之间有相同的代码
父类 : Animal
子类 : Dog、Person
#动物 class Animal: def __init__(self,name,aggressivity,life_value): self.name = name self.aggressivity = aggressivity self.life_value = life_value #狗 class Dog(Animal): # 定义一个狗类 def bite(self,people): people.life_value -= self.aggressivity #人 class Person(Animal): # 定义一个人类 def attack(self,dog): dog.life_value -= self.aggressivity def get_weapon(self,weapon_obj): if self.money > weapon_obj.price: self.money -= weapon_obj.price # 金老板花钱买武器 self.weapon = weapon_obj # 金老板装备打狗棒 self.aggressivity += weapon_obj.aggr # 金老板的攻击力增加了 huang = Dog('大黄',100,3000) #__init__ 找父类 print(huang.life_value) boss_gold = Person('金老板',5,250) #__init__ 自己没有 找父类 print(boss_gold.life_value) # Dog.bite(Person) print(Dog.__bases__) #继承于Animal类,(<class '__main__.Animal'>,) print(Animal.__bases__) #继承于object,(<class 'object'>,)
__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
python 两种类:经典类 新式类
python3 新式类 —— 都默认继承object class Animal(object) == class Animal:
python2 经典类和新式类并存
class Animal: 经典类
class Animal(object): 新式类
两个类中有相同的代码,
继承:把相同的代码放在父类中,子类的对象在子类中没有找到方法的时候,使用父类的
单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
抽象与继承
抽象即抽取类似或者说比较像的部分,抽象最主要的作用是划分类别。
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
class Animal: def eat(self): print('eating') def drink(self): print('drinking') def sleep(self): print('sleeping') class Cat(Animal): def catch_mouse(self): print('yeah') class Dog(Animal): def watch_door(self): print('wangwangwang') kitty = Cat() kitty.eat() snoopy = Dog() snoopy.eat()
人类 狗类 相同的属性 提取了一个__init__方法,在这个方法里放一些共有的属性
猫类和狗类 相同的方法 直接把相同的方法提取出来,放在基类里
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
class Animal: def __init__(self,aggressivity, life_value,name): self.name = name # 每一个角色都有自己的昵称; self.aggressivity = aggressivity # 每一个角色都有自己的攻击力; self.life_value = life_value # 每一个角色都有自己的生命值; def eat(self): self.life_value += 10 class Person(Animal): def __init__(self, name, aggressivity, life_value, money): Animal.__init__(self, name, aggressivity, life_value) self.money = money #派生属性:父类没有的属性 def attack(self,dog): dog.life_value -= self.aggressivity def get_weapon(self,weapon_obj): if self.money > weapon_obj.price: self.money -= weapon_obj.price # 金老板花钱买武器 self.weapon = weapon_obj # 金老板装备打狗棒 self.aggressivity += weapon_obj.aggr # 金老板的攻击力增加了 class Dog(Animal): def __init__(self, name, breed, aggressivity, life_value): Animal.__init__(self,aggressivity,life_value,name) self.breed = breed # 每一只狗都有自己的品种; #派生属性:父类没有的属性 def bite(self,people): # 派生方法 :父类没有的方法 people.life_value -= self.aggressivity def eat(self): Animal.eat(self) print('dog is eating') snoopy = Dog('太白','京巴',250,500) print(snoopy.breed) print(snoopy.name) # Animal.eat(snoopy) snoopy.eat() print(snoopy.life_value) snoopy.eat() print(snoopy.life_value)
派生类
派生属性 : 在自己的init方法里 使用父类的init方法 —— 指名道姓调用方法
派生方法 : 在子类中增加父类没有的
只要子类有,就用子类的
只要想用父类,父类名.父类的方法(子类对象)
在新式类中
class Animal: def __init__(self,aggressivity, life_value,name): self.name = name # 每一个角色都有自己的昵称; self.aggressivity = aggressivity # 每一个角色都有自己的攻击力; self.life_value = life_value # 每一个角色都有自己的生命值; def eat(self): self.life_value += 10 class Person(Animal): def __init__(self, name, aggressivity, life_value, money): # Animal.__init__(self, name, aggressivity, life_value) super().__init__(name, aggressivity, life_value) #新式类 self.money = money #派生属性:父类没有的属性 def attack(self,dog): dog.life_value -= self.aggressivity def get_weapon(self,weapon_obj): if self.money > weapon_obj.price: self.money -= weapon_obj.price # 金老板花钱买武器 self.weapon = weapon_obj # 金老板装备打狗棒 self.aggressivity += weapon_obj.aggr # 金老板的攻击力增加了 class Dog(Animal): def __init__(self, name, breed, aggressivity, life_value): # Animal.__init__(self,aggressivity,life_value,name) # super(Dog,self).__init__(aggressivity,life_value,name) super().__init__(aggressivity,life_value,name) self.breed = breed # 每一只狗都有自己的品种; #派生属性:父类没有的属性 def bite(self,people): # 派生方法 :父类没有的方法 people.life_value -= self.aggressivity def eat(self): # Animal.eat(self) super().eat() print('dog is eating') snoopy = Dog('太白','京巴',250,500) print(snoopy.breed) print(snoopy.name) snoopy.eat() print(snoopy.life_value) super(Dog,snoopy).eat() #Animal.eat(snoopy) print(snoopy.life_value)
用子类的对象,调用父类的方法:
如果子类中没有这个方法,直接就使用父类的
如果子类中有同名方法:
经典类 指名道姓 类名.方法名(子类对象) 类内外一致
新式类 super方法 super(子类名,子类对象).方法名() 类内可以省略super的参数
经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
class Foo: def __init__(self): self.func() def func(self): print('Foo.func') class Son(Foo): def func(self): print('Son.func') s = Son() 结果Son.func
钻石继承问题
经典类和新式类的多继承问题,继承顺序问题
经典类 : 深度优先
新式类 :广度优先
class F(object): pass def f(self): print('F') class E(F): pass def f(self): print('E') class D(F): pass def f(self): print('D') class B(D): pass def f(self): print('B') class C(E): pass def f(self): print('C') class A(B,C): pass def f(self): print('A') a = A() a.f()
class c:经典类
class c(object):新式类
print(F.__mro__) #只有新式才有这个属性可以查看继承顺序,经典类没有这个属性
python3中统一都是新式类 pyhon2中才分新式类与经典类
经典类:
新式类:
多态
python不支持多态,但是它本事具有多态性
在面向对象方法中一般是这样表述多态性: 向不同的对象发送同一条消息(obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。 也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
鸭子类型
具有具有相同特征的对象,比如列表和元组
抽象类和接口类
接口类:多继承。父类不实现,是规范子类的模板,只要接口类中定义的,就应该在子类中实现
接口类不能被实例化,它只能被继承,支持多继承
from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): #模板,接口类 @abstractmethod #装饰接口类中方法的,加上这个装饰器,自动检测子类中的方法名 def pay(self,money):pass @abstractmethod def get(self):pass class Apple_Pay(Payment): def pay(self,money): print('您使用苹果支付支付了%s元'%money) class Ali_Pay(Payment): def pay(self, money): print('您使用支付宝支付了%s元' % money) class WeChat_Pay(Payment): def pay(self,money): print('您使用微信支付了%s元' % money) def pay(obj,money): return obj.pay(money) # apple = Apple_Pay() # ali = Ali_Pay() # wechat = WeChat_Pay() # pay(apple,100) #apple.pay(100) # pay(wechat,200)
from abc import ABCMeta,abstractmethod class Fly_Animal(metaclass=ABCMeta): #规范 @abstractmethod def fly(self):pass class Swim_Animal(metaclass=ABCMeta): @abstractmethod def swim(self): pass class Walk_Animal(metaclass=ABCMeta): @abstractmethod def walk(self): pass class Frog(Walk_Animal,Swim_Animal): def walk(self): print('自己实现walk功能') def swim(self): pass class Swan(Walk_Animal,Swim_Animal,Fly_Animal): pass class Bird(Walk_Animal,Fly_Animal): pass
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Alipay: ''' 支付宝支付 ''' def pay(self,money): print('支付宝支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) def pay(payment,money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 ''' payment.pay(money) p = Alipay() pay(p,200)
开发中容易出现的问题
class Alipay: ''' 支付宝支付 ''' def pay(self,money): print('支付宝支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) class Wechatpay: def fuqian(self,money): ''' 实现了pay的功能,但是名字不一样 ''' print('微信支付了%s元'%money) def pay(payment,money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 ''' payment.pay(money) p = Wechatpay() pay(p,200) #执行会报错
接口初成:手动报异常:NotImplementedError来解决开发中遇到的问题
class Payment: def pay(self): raise NotImplementedError class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #这里不报错 pay(p,200) #这里报错了
借用abc模块来实现接口
from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): @abstractmethod def pay(self,money): pass class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #不调就报错了
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
依赖倒置原则: 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:
抽象类
什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
在python中实现抽象类
#一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
抽象类与接口类
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
1.多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口
接口隔离原则: 使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
2.方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
小结:
抽象类
抽象类可以实现一些子类共有的功能和属性抽象类不鼓励多继承
文件操作 :打开文件 关闭文件 写文件 读文件
硬盘操作:打开,关闭,写 读
进程文件:打开 关闭,读 写
python 没有接口的概念
只能借助抽象类的模块 来实现接口类
接口 —— java : 没有多继承 —— Interface
from abc import ABCMeta,abstractmethod class Base(metaclass=ABCMeta): def __init__(self,filename): self.filename = filename @abstractmethod #抽象方法 def open(self): return 'file_handler' @abstractmethod def close(self):pass @abstractmethod def read(self):pass @abstractmethod def write(self):pass class File(Base): def open(self):pass def close(self):pass def read(self):pass def write(self):pass
抽象类不能被实例化
这个抽象类可以规范子类必须实现抽象类中的抽象方法
封装
封装 :把一些属性和方法放到类里 这本身就是一种封装
把属性和方法藏在类里,我只能在类内部调用,不能在外部使用
class Dog: __role = 'dog' #私有的静态属性 def __discount(self): #私有的方法 print('in __func') def price(self): self.__discount() print(Dog.__dict__) print(Dog._Dog__role) #从类的外面不能直接调用,在类内的使用加上了一层密码:_类名 d = Dog()
定义一个私有变量属性方法 : __名字
在类的内部可以直接用 : __名字
在类的外部不能直接使用,如果一定要用,在私有方法之前加上:_类名,变成 _类名__名字
在类外的名字 通过__dict__就可以查看
class Room: def __init__(self,name,price,length,width): self.name = name self.price = price self.__length =length #私有的对象属性 self.__width = width def area(self): return self.__length*self.__width house = Room('小超超',1000000,2,1) print(house.area()) class A: def __func(self): print('__a_func') class B(A): def __init__(self): self.__func() b = B()
私有变量和私有方法
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
这种变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
私有方法
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
私有的静态属性、方法、对象属性
使用__名字的方式调用,保证在类内部可以调用,外部不行
私有的不能被继承
当有一个名字,不想被外部使用也不想被子类继承,只想在内部使用的时候就定义私有的
property属性
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解) 成人的BMI数值: 过轻:低于18.5 正常:18.5-23.9 过重:24-27 肥胖:28-32 非常肥胖, 高于32 体质指数(BMI)=体重(kg)÷身高^2(m) EX:70kg÷(1.75×1.75)=22.86class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight / (self.height**2) p1=People('李岩',1.75,85)
print(p1.bmi)
将圆的周长和面积方法伪装成属性
class Circle: def __init__(self,radius): self.radius=radius @property def area(self): return math.pi * self.radius**2 @property def perimeter(self): return 2*math.pi*self.radius c=Circle(10) print(c.radius) print(c.area) #可以向访问数据属性一样去访问area print(c.perimeter)
注意:此时的特性area和perimeter不能被赋值 c.area=3 #为特性area赋值 抛出异常: AttributeError: can't set attribute
@property把一个方法伪装成一个属性
1.属性的值是这个方法的返回值
2.这个方法不能有参数
封装
__私有+property
让对象的属性变得更安全了
获取到的对象的值可以进行一些加工
修改对象的值的同时可以进行一些验证
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo: @property def AAA(self): print('get的时候运行我啊') @AAA.setter def AAA(self,value): print('set的时候运行我啊') @AAA.deleter def AAA(self): print('delete的时候运行我啊') #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
class Foo: def get_AAA(self): print('get的时候运行我啊') def set_AAA(self,value): print('set的时候运行我啊') def delete_AAA(self): print('delete的时候运行我啊') AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
#打折
class Goods: __discount = 0.8 #静态属性 def __init__(self,name,price): self.__name = name self.__price = price #原价 @property def name(self): return self.__name @name.setter def name(self,new_name): self.__name = new_name @name.deleter def name(self): del self.__name @property def price(self): #折后价 return self.__price * Goods.__discount @price.setter def price(self,new_price): #修改原价 if type(new_price) is int: self.__price = new_price apple = Goods('苹果',10) # del apple.name # print(apple.name)
classmethod类方法
#定义一个类 #类里面的方法 #并没有用到self class Goods: __discount = 0.8 @classmethod #类方法 def change_discount(cls,new_discount): cls.__discount = new_discount @classmethod def get_discount(cls): return cls.__discount # apple = Goods() Goods.change_discount(0.75) print(Goods.get_discount())
类方法
调用:不需要实例化 直接用类名调用就好
定义:不用接受self参数,默认传cls,cls就代表当前方法所在的类
需要使用静态变量,且不需要和对象相关的任何操作的时候用类方法
如果这个方法既不需要操作静态变量,也不需要使用对象相关的操作,就使用静态方法
class A: @staticmethod def func(name): #静态方法 print(123) A.func('alex')
面向对象编程:专门为面向对象编程提供的一个方法——staticmethod
它完全可以当做普通函数去用,只不过这个函数要通过 类名.函数名 调用
其他的比如传参和返回值和普通函数完全没有区别
类里面,一共可以定义这三种方法:
1,普通方法 self
2,类方法 cls
3,静态方法
绑定方法和非绑定方法
class A: @staticmethod def func1(name): print(123) @classmethod def func2(cls): print(123) def func3(self):pass a = A() print(a.func1) #静态方法 print(a.func2) #类方法 : 绑定到A类的func print(a.func3) #普通方法:绑定到A类对象的func
结果:
<function A.func1 at 0x00000230A9C888C8>
<bound method A.func2 of <class '__main__.A'>>
<bound method A.func3 of <__main__.A object at 0x00000230A9C8A5C0>>
静态方法和类方法都直接可以使用类名调用,也可以使用对象调用
普通方法:只能使用对象调用
总结
# 普通方法 self
# 静态方法 @staticmethod
# 类方法 @classmethod
# 属性方法 @property
# 所有的私有的 都不能在类的外部使用
# 所有的私有 都是在变量的左边加上双下划綫
# 对象的私有属性
# 类中的私有方法
# 类中的静态私有属性
# 所有的私有的 都不能在类的外部使用
# 接口类 抽象类
# python中没有接口类,有抽象类,abc模块中的metaclass = ABCMeta,@abstructmethod
# 本质是做代码规范用的,希望在子类中实现和父类方法名字完全一样的方法
# python及支持单继承也支持多继承,所以对于接口类和抽象类的区别就不那么明显了
# 甚至在python中没有内置接口类
# 多态和鸭子类型
# 多态 —— python天生支持多态
# 鸭子类型 —— 不依赖父类的情况下实现两个相似的类中的同名方法
# 封装 —— 私有的
# 在python中只要__名字
# 在python中只要__名字,就把这个名字私有化了
# 私有化了之后 就不能能从类的外部直接调用了
# 静态属性 方法 对象属性 都可以私有化
# 这种私有化只是从代码级别做了变形,并没有真的约束
# 变形机制 _类名__名字 在类外用这个调用,在类的内部直接__名字调用
父类的私有属性 bu 能被 子类调用
# 会用到私有的这个概念de场景
#1.隐藏起一个属性 不想让类的外部调用
#2.我想保护这个属性,不想让属性随意被改变
#3.我想保护这个属性,不被子类继承