面向对象基础
初识面向对象
概述
- 面向过程: 根据业务逻辑从上到下 垒代码
- 函数式: 将某功能代码封装到函数中,日后便无需重复编写,直接调用函数即可。
- 面向对象:将对象进行分类和封装。让开发更快更好更强
创建类和对象
面向对象编程(oop),是一种编程方式。此编程方式的落地需要使用’类‘和’对象‘来实现,所以,面向对象编程其实就是对’类‘和’对象‘的使用。
class 类名:
属性 = 'a'
对象 = 类()
类名的作用就是 操作属性,查看属性。
类的定义
类就是一个模板,模板里可以包含多个函数,函数里实现一些功能。
对象则是根据模板创建的实例,通过实例对象可以执行类中的函数。
class Person: # 类名
country = 'China' # 创造了一个只要是这个类就一定有的属性。类属性,静态属性
def __init__(self,*args): #初始化方法。self是对象,是一个必须传的参数。
#self就是一个可以存储很多属性的大字典。self拥有的属性都属于对象。
self.name = 'nick'
# 方法,一般情况下必须传self参数,且必须写在第一个
# 后面还可以传其它参数,
def walk(self,n):
print(f'{self.name}走了{n}步')
print(Person.country) # 类名 可以查看类中的属性,不需要实例化即可查看
nick = Person('nick') # 类名可以实例化对象。nick对象
print(nick.name) # 查看所有的属性,通过 . 点出
nick.walk() # Person.walk(nick,5) # 调用方法 类名.方法名(对象名)
示例
class Person:
country = 'China'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def walk(self,n):
print(f'{self.name}都{self.age}岁了,还没走过{n}步。')
nick = Person('nick',5,'man')
nick.walk(1)
print(nick.name)
print(nick.age)
print(nick.sex)
- 定义一个类,必须class+类名:
- 类里可以定义静态属性,这个属性可以直接使用使用类名调用,也称类属性
- 类里可以定义动态属性,也就是方法。每个方法都有一个必须传的self属性。类调用这个方法时,需要自己传self,实例化后的对象会自动传self
__init__
方法是类的初始化方法。- 实例化的时候,python帮我们创建了一个对象self
- 每当我们调用类的时候就会自动触发这个方法。默认传self
- 在init方法里面可以对self进行赋值
- self是什么。
- self就是一个大字典
- self拥有的属性都属于对象
- 在类的内部,self就是一个对象
实例化
对象 = 类名()
过程:
- 类名() 首先会创造出一个对象,创建了一个self变量
- 调用
__init__
方法,类名括号里的参数会被这里接收 - 执行
__init__
方法 - 返回self
对象能做什么
- 对象可以查看类中的属性,
- 可以调用类中所用的方法。
__dict__
对于对象的增删改查都可以通过字典的语法 进行
类名能做什么
- 实例化对象
- 调用方法:只不过要自己传递self参数
- 调用类中的属性,也就是调用静态属性
__dict__
对类中的名字只能看 不能操作
# 代码演示
# 人狗大战
class Dog:
def __init__(self,name,blood,aggr,kind):
self.name = name
self.hp = blood
self.aggr = aggr
self.kind = kind
def bite(self,person):
# 狗咬人,人掉血
person.blood -= self.aggr
class Person:
def __init__(self,name,blood,aggr,sex):
self.name = name
self.blood = blood
self.aggr = aggr
self.sex = sex
def attack(self,dog):
dog.hp -= self.aggr
if dog.hp <= 0:
print('%s打了%s,%s被打死了,扑街~~~'%(self.name,dog.name,dog.name))
else:
print('%s打了%s,掉了%s血'%(self.name,dog.name,self.aggr))
jin = Dog('金老板',100,20,'teddy')
# print(jin.name)
alex = Person('alex',999,998,'不详')
jin.bite(alex) # Dog.bite(jin,alex)
print(alex.blood)
# alex attack
alex.attack(jin) # Person.attack(alex,jin)
print(jin.hp)
类和对象的命名空间
类里面我们可以定义两种属性:静态属性和动态属性。
类中的静态属性 可以被对象和类 调用。
- 对于不可变数据类型来说,类变量最好用类名操作
- 对于可变数据类型来说,对象名的修改是共享的,重新赋值是独立的。(对象名的修改,每个实例化后的对象也随着更改,重新赋值的话,只有本对象的数据被重新赋值,其他实例化出来的数据是不变的)
扩展
创建一个类,每实例化一个对象就计数,最终所有的对象共享这个数据。
class Foo:
count = 0
def __init__(self):
count += 1
f1 = Foo()
f2 = Foo()
print(f1.count)
print(f2.count)
f2 = Foo()
print(f1.count)
绑定方法
class Foo:
def func(self):
pass
print(Foo.func) #<function Foo.func at 0x055AA4B0>
f1 = Foo()
print(f1.func) #<bound method Foo.func of <__main__.Foo object at 0x05519EB0>>
包
创建包的时候,会自动创建一个__init__.py
文件
导入一个模块,就相当于类的实例化的过程
面向对象的三大特性
封装,继承,多态
封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。对代码的保护,不让别人看见。
所以,在使用面向对象的封装特性时,需要
- 将内容封装到某处
- 从某处调用被封装的内容
第一步:将内容封装到某处
self是一个形参,当执行实例化时,self就是实例化后的对象,所以内容其实被封装到了对象中,每个对象中都有类的属性,在内存里面类似于下图来保存。
第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
-
通过对象直接调用
-
通过self间接调用
class Foo: def __init__(self, name, age): self.name = name self.age = age def detail(self): print self.name print self.age obj1 = Foo('wupeiqi', 18) obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
总结:对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或self间接获取被封装的内容。
class Person:
__key = 123 # 双下划线,表示私有。这里是私有静态属性
def __init__(self,name,passwd):
self.name= name
self.__passwd= passwd #私有属性
def __get_pwd(self): # 私有方法
return self.__passwd # 只要在类的内部使用私有属性,就会自动的带上_类
def login(self):
return self.__get_pwd() # 正常的方法调用私有的方法
alex = Person('alex','alex3714')
print(alex._Person__passwd) # _类名__属性名
print(alex.get_pwd()) # 这种调用是错误的,正确方式如下
print(alex.login()) #要使用正常的方法调用私有的方法
# 所有的私有 都是在变量的左边加上双下划线
# 对象的私有属性
# 类中的私有方法
# 类中的静态私有属性
# 所有的私有的,都不能在类的外部使用
继承
继承,即子类继承父类的内容。
所以,对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必自己再一个一个的实现每个方法。
一个类可以被 多个类 继承,一个类可以继承多个类
被继承的类 称之为 父类、基类、超类
继承父类的类 称之为 子类,派生类
python3里都是 新式类,没有继承父类的类都默认继承object
父类中没有的属性,在子类中出现 ,叫做 派生属性
父类中没有的方法,在子类中出现,叫做 派生方法
只要是子类的对象调用,子类中有的名字一定是用子类的,子类中没有才找父类的。如果父类也没有这个名字,就报错。
如果父类中的方法和属性 子类中都有,那就用子类的。如果你仍然想用父类的,需要单独调用父类的这个方法或属性。
- 父类.方法名(self,...) 需要自己传self参数
- super().方法名 不需要自己传self---只有新式类中可以使用这种方法
正常的代码中, 使用单继承,可以减少代码的重复
继承表达的是一种 子类 是父类的关系。
注意:上述的查找过程中,一旦找到,则立即中断寻找过程,不会继续找下去了。
多态
python不支持Java和c# 这一类强类型语言中多态的写法,但是原生多态,其python崇尚’鸭子类型‘
class F1:
pass
class S1(F1):
def show(self):
print 'S1.show'
class S2(F1):
def show(self):
print 'S2.show'
def Func(obj):
print obj.show()
s1_obj = S1()
Func(s1_obj)
s2_obj = S2()
Func(s2_obj)
Python “鸭子类型”
总结
- 面向对象是一种编程方式,此编程方式的实现是基于 类和对象的使用。
- 类 是一个模板,模板中包装了多个’函数‘供使用。
- 对象,根据模板创建的实例(即 对象),对象用于调用被包装在类中的函数
- 面向对象的三大特性:封装、继承和 多态
组合的用法
一个对象的属性值 是另外一个类的对象。
# 组合
# 人狗大战
class Dog:
def __init__(self,name,aggr,hp,kind):
self.name = name
self.aggr = aggr
self.hp = hp
self.kind = kind
def bite(self,person):
person.hp -= self.aggr
class Person:
def __init__(self,name,aggr,hp,sex):
self.name = name
self.aggr = aggr
self.hp = hp
self.sex = sex
self.money = 0
def attack(self,dog):
dog.hp -= self.aggr
def get_weapon(self,weapon):
if self.money >= weapon.price:
self.money -= weapon.price
self.weapon = weapon
self.aggr += weapon.aggr
else:
print("余额不足,请先充值")
class Weapon:
def __init__(self,name,aggr,njd,price):
self.name = name
self.aggr = aggr
self.njd = njd
self.price = price
def hand18(self,person):
if self.njd > 0:
person.hp -= self.aggr * 2
self.njd -= 1
alex = Person('alex',0.5,100,'不详')
jin = Dog('金老板',100,500,'teddy')
w = Weapon('打狗棒',100,3,998)
# alex装备打狗棒
alex.money += 1000
alex.get_weapon(w)
print(alex.weapon)
print(alex.aggr)
alex.attack(jin)
print(jin.hp)
alex.weapon.hand18(jin)
print(jin.hp)
# 组合 :一个对象的属性值是另外一个类的对象
# alex.weapon 是 Weapon类的对象
继承
多继承
新式类中的继承顺序: 广度优先
经典类中的继承顺序是 : 深度优先,python2 版本
在单继承中,子类有的用子类的,没有就用父类的。
在多继承中,默认是就近原则,在经典类中是按照深度优先进行继承,而在新式类中是按照广度优先的顺序进行查找。
在python2中新式类和经典类是共存的,新式类要继承object。
在python3 只有新式类,默认继承object
经典类和新式类的区别
多继承寻找名字的顺序:新式类广度优先,经典类深度优先
新式类中有一个类名.mro 方法,查看广度优先的继承顺序
python3 中 有个super 方法,根据广度优先的继承顺序查找上一个类
super
只在python3 中存在,它的本质是: 不是单纯的找父类,而是根据调用者的节点位置的广度优先顺序来找名字的。