Python面向对象
python面向对象
part1
面对对象编程
- 类里面一个tab键类会执行
class Person:#类名
def__init__(self,name,sex,job,level,hp,weapon,ad):
#必须叫__init__这个名字,不能改变的,所有的在一个具体的人物出现之后拥有的属性,都写在这里
# self是具体的对象 这里self就是alex
self.name=name
self.sex=sex
self.job=job
self.level=level
self.hp=hp
self.weapon=weapon
self.ad=ad
#print(self,self.__dict__)
alex=Person('alex','不详','搓澡工',0,250,'搓澡巾',1) # alex就是对象alex=Person()的过程是通过类获取一个对象的过程--->实例化
print(alex,alex.__dict__) #__dict__查看类中的属性和值
- 类名()会自动调用类中的__init__( )方法
print(alex.name) #属性的查看等同于print(alex.__dict__['name'])
alex.name='alexsb' #属性的修改
alex.money=10000 #属性的增加
del alex.money #属性的删除
实例化经历步骤/面向对象编程理解
- 类和对象之间的关系?
- 类是一个大范围是一个模子他约束了事物有哪些属性,但是不能约束具体的值
- 对象是一个具体的内容是模子的产物它遵循了类的约束同时给属性赋上具体的值
- Person是一个类 alex wusir都是这个类的对象
- 类有一个空间,存储的是定义在class中的所有名字
- 每一个对象又拥有自己的空间,通过对象名.__dict__就可以查看这个对象的属性和值
- 实例化所经历的步骤:
- 类名()之后的第一件事情:开辟一块内存空间(重要)
- 调用__init__把空间的内存地址作为self参数传递到函数内部
- 所有的这一个对象需要使用的属性都需要和self关联起来
- 执行完__init__中的逻辑后,self变量会自动的被返回到调用处(发生实例化的地方)
- 补充:修改对象/列表/字典中的某个值,或者是对象的某一个属性,都不会影响这个对象/列表/字典所在的内存空间
初识方法
class Person:#类名
def__init__(self,name,sex,job,level,hp,weapon,ad):
#必须叫__init__这个名字,不能改变的,所有的在一个具体的人物出现之后拥有的属性,都写在这里
#self是具体的对象
self.name=name#对象的属性/实例变量
self.sex=sex
self.job=job
self.level=level
self.hp=hp
self.weapon=weapon
self.ad=ad
def cuo(self,dog):#方法,有一个必须传的参数self
print('whaha',self)
print(self.name)
dog.hp-=self.ad
print(f"{self.name}给{dog.name}搓澡{dog.name}掉了{self.ad}点血")
alex=Person('alex','不详','搓澡工',0,250,'搓澡巾',1)#对象/实例=类名()--->实例化过程
#print('alex',alex)
#alex.cuo(xiaobai)
part2:
-
python中的类型
-
类:这个类有什么属性用什么方法大致的样子,不能知道具体的属性对应的值
-
对象:之前所有的属性值就都明确了
-
python中一切皆是对象,处处是多态
-
类型:int float str dict list tuple set----类(内置的数据类型,内置的类)
-
变量名=xx数据类型对象
-
所有的对象都有一个类型,class A实例化出来的对象的类型就是A类
命名空间问题
- 类中的变量是静态变量
- 对象中的变量只属于对象本身,每个对象都有自己的空间来存储对象的变量
- 当使用对象名去调用某一个属性的时候会优先在自己的空间中寻找,找不到在到类中寻找
- 如果自家没有就引用类的,如果类中也没有就报错
- 对于类来说,类中的变量所有的对象都可以读取的,并且读取的是同一份变量
class A:#把所有都放进去时A才指向内存空间
Country='中国' #静态变量/静态属性存储从在类的名称空间里#存的是变量的时候A.__dict__是值
def__init__(self,name,age,country): #绑定方法存储从在类的名称空间里
self.name=name
self.age=age
self.country=country
def func1(self): #存的是函数的时候(A.__dict__)是地址
print(self)
def func2(self):
pass
案例1:
a=A('alex',83,'印度人')
b=A('wusir',74,'泰国人')
a.Country='日本人'
print(a.Country) #日本人
print(b.Country) #泰国人#从对象的命名空间取值
print(A.Country) #中国#从类的名称空间取值
组合
- 组合:一个类的对象是例外一个类对象的属性---->对象变成了一个属性
#学生类
#班级信息:班级名字开班时间当前讲师
class Student:
def__init__(self,name,age,sex,calc):
self.name=name
self.age=age
self.sex=sex
self.calc=calc
class Clas:
def__init__(self,cname,begin_time,teacher):
self.cname=cname
self.begin_time=begin_time
self.teacher=teacher
py22=Clas('python23','2019-5-28','eva-j')
py23=Clas('python22','2019-4-26','eva-j')
大壮=Student('大壮',18,'male',py22)
雪飞=Student('雪飞',25,'female',py23)
print(大壮.calc,py22) #大壮.calc等同于py22
print(py22.begin_time)
print(大壮.calc.begin_time)
part3:
继承
继承--->需要解决代码的重复
class A:
pass
class B(A):
pass
B继承A,A是父类,B是子类
A是父类基类超类
B是子类派生类
- 子类可以使用父类中的:方法 静态变量
- 先开辟空间,空间中有一个类指针-->指向Cat
- 调用init,对象在[自己的空间中找init没找到],到Cat类中找init也没有找到
- 找父类Animal中的init
- 当子类和父类方法重名的时候,我们只使用子类的方法,而不会调用父类的方法
-
-
- 子类想要调用父类的方法的同时还想要执行自己的同名方法
- 猫和狗在调用eat的时候即调用自己的也调用父类的
- 在子类的方法中调用父类的方法:父类名.方法名(self)
- 通过Animal.eat(self)
class Cat (Animal):
#def__init__(self,age):
#self.age=18
def eat(self):
self.blood+=100
Animal.eat(self)
def climp_tree(self):
print(f'{self.name}isclamping')
self.sleep()
继承2/多继承
- 父类和子类方法的选择:
- 子类的对象,如果去调用方法,永远优先调用自己的:
- 如果自己有用自己的
- 自己没有用父类的
- 如果自己有还想用父类的:直接在子类方法中调父类的方法父类名.方法名(self)
- 子类的对象,如果去调用方法,永远优先调用自己的:
面试题:
class Foo:
def__init__(self):
self.func()#在每一个self调用func的时候,我们不看这句话是在哪里执行,只看self是谁
def func(self):
print('in foo')
class Son(Foo):
def func(self):
print('in son')
#Son()
- 多继承(java里面不支持多继承)
- python语言特点:可以在面向对象中支持多继承
class B:
def func(self):
print('inB')
class A:
def func(self):
print('inC')
class C(B,A): #(谁离他近,先计算谁)
pass
C().func()
- 单继承:
- 调子类的:子类自己有的时候
- 调父类的:子类自己没有的时候
- 调子类和父类的:子类父类都有,在子类中调用父类的
- 多继承:
- 一个类有多个父类,在调用父类方法的时候,按照继承顺序,先继承的就先寻找
知识点的补充
- object类 类祖宗
- 所有在python3当中的类都是继承object类的
- object中有init
- 所有的类都默认的继承object
-
-
- isinstance(判断数据类型)判断类与类之间的继承关系的
- type(查看数据类型) [使用types模块确认方法和函数的区别]
绑定方法和普通函数
方法与类的区别不在于是否写在类里面,而是在于谁去调用它
from types import FunctionType,MethodType
FunctionType:判断是否是函数
MethodType:判断是否是方法
class A:
def func(self):
print('infunc')
print(A.func) #函数-->用类名调用时函数
a=A()
print(a.func)#方法-->用对象调用是方法
print(isinstance(a.func,FunctionType))
print(isinstance(a.func,MethodType))
- 记忆:
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
pickle补充
- 将对象利用pickle存储在文件中
- pickle可以连续的drump进去/连续load出来
class Course:
def__init__(self,name,period,price):
self.name=name
self.period=period
self.price=price
python=Course('python','6个月',21800)
linux=Course('linux','5个月',18800)
go=Course('go','4个月',11800)
import pickle
with open('pickle_file','ab')asf:
pickle.dump(python,f)
pickle.dump(linux,f)
pickle.dump(go,f)
with open('pickle_file',mode='rb')asf1:
while True:
try:
obj=pickle.load(f1)
print(obj.name,obj.period)
except EOFError:
break
part4:
类的继承关系
- python3所有的类都是继承Object类
- 只要继承object类就是新式类
- python3所有的类都是新式类
- 经典类:在py3中不存在,在py2中不继承object的类都是经典类,继承object的不是
在py2中
class A:pass #经典类
class B(object):pass #新式类
在py3中
class A:pass #新式类
class B(object):pass #新式类
- 在新式类中,广度优先在走到一个点,下一个点既可以从深度走,也可以从广度走的时候,总是先走广度,再走深度,广度优先
- 在经典类中,都是深度优先,总是在一条路走不通之后再换一条路,走过的点不会再走了(没有mro)
- 广度优先遵循C3算法,要会用mro,会查看顺序
- 经典类没有mro,但新式类有
o
A
B C
D E
F
C3算法
A(O)=[AO]
B(A)=[BAO]
C(A)=[CAO]
D(B)=[DBAO]
E(C)=[ECAO]
F(D,E)=merge(D(B)+E(C))
=[F]+[DBAO]+[ECAO]
F=[DBAO]+[ECAO]
FD=[BAO]+[ECAO]
FDB=[AO]+[ECAO]
FDBE=[AO]+[CAO]
FDBEC=[AO]+[AO]
FDBECA=[O]+[O]
FDBECAO
算法的内容:
如果是单继承那么总是按照从子类->父类的顺序来计算查找顺序
如果是多继承需要按照自己本类,父类1的继承顺序,父类2的继承顺序,...
merge的规则:如果一个类出现在从左到右所有顺序的最左侧,并且没有在其他位置出现,那么先提出来作为继承顺序中的一个
或一个类出现在从左到右顺序的最左侧,并没有在其他顺序中出现,那么先提出来作为继承顺序中的一个
如果从左到右第一个顺序中的第一个类出现在后面且不是第一个,那么不能提取,顺序向后继续找其他顺序中符合上述条件的类
抽象类(父类对子类的约束)
- 补充:
- raise KeyError('提示信息')#主动抛异常
- raise NotImplementedError('没有按照要求实现函数')
- 方式1:手动抛出异常一般用这个
step1: class Payment:#抽象类
def pay(self,money):
'''只要你见到了项目中有这种类,你要知道你的子类中必须实现和pay同名的方法'''
raise NotImplementedError('请在子类中重写同名pay方法')
step2: 其他类继承该类并且要实现pay()方法
step3:归一化处理:
def pay(name,price,kind):
if kind=='Wechat':
obj=WeChat(name)
elif kind=='Alipay':
obj=Alipay(name)
elif kind=='Apple':
obj=Apple(name)
obj.pay(price)
pay('alex',400,'Wechat')
pay('alex',400,'Alipay')
pay('alex',400,'Apple')
appa=Apple('alex')
appa.fuqian(500)
方式1缺点,实例化时不能报错,只有调用方法时才会报错
- 方式2:利用abc模块
- 实现抽象类的另一种方式,约束力强,依赖abc模块
step1:
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self,money):#该方法一定要实现,且同名
'''只要你见到了项目中有这种类,你要知道你的子类中必须实现和pay同名的方法'''
raise NotImplementedError('请在子类中重写同名pay方法')
step2: 其他类继承该类并且要实现pay()方法
多态和鸭子类型
- 背
- 一个类型表现出来的多种状态(通过继承来实现)
- 支付表现出的微信支付和苹果支付这两种状态
- 在java情况下:一个参数必须制定类型
- 所以如果想让两个类型的对象都可以传,那么必须让这两个类继承自一个父类,在制定类型的时候使用父类来指定
- 案例如下:(java中)
class lentype:pass
class list(lentype):pass
class dict(lentype):pass
class set(lentype):pass
class tuple(lentype):pass
class str(lentype):pass
def len(lentype obj):
pass
len(list)
len(dict)
len(set)
len(tuple)
len(str)
- 鸭子类型:
- 所有实现了__len__方法的类,在调用len函数的时候,obj都说是鸭子类型
- 迭代器协议__iter____next__是迭代器
案例:
class list:
def__len__(self):pass
class dict:
def__len__(self):pass
class set:
def__len__(self):pass
class tuple:
def__len__(self):pass
class str:
def__len__(self):pass
def len(鸭子类型看起来有没有实现一个__len__obj):
return obj.__len__( )
part5:
super方法
-
super是按照mro的顺序来寻找当前类的下一个类的
-
在py3中不需要传参数,自动就帮我们寻找当前类的mro顺序的下一个类中的同名方法
-
在py2中的新式类中,需要我们主动传递参数super(子类的名字,子类的对象).函数名()
这样才能够帮我们调用到这个子类的mro顺序的下一个类中的方法 -
在py2的经典类中,并不支持使用super来找下一个类
-
在D类中找super的func,那么可以这样写super().func()
也可以这样写super(D,self).func()(并且在py2的新式类中必须这样写) -
在单继承的程序中,super就是找父类
class User:
def__init__(self,name):
self.name=name
class VIPUser(User):
def__init__(self,name,level,strat_date,end_date):
#User.__init__(self,name)#可以不继承
super().__init__(name)#推荐的用super必须要继承执行父类的同名方法
#super(VIPUser,self).__init__(name)
self.level=level
self.strat_date=strat_date
self.end_date=end_date
太白=VIPUser('太白',6,'2019-01-01','2020-01-01')
print(太白.__dict__)
super方法补充
- super方法有参数问题:1.参数是自己本身,调用同super(),2.参数是其他的子类,按照mro顺序调用它的上一个类的方法
class A:
def f1(self):
print('in A f1')
class B(A):
def f1(self):
print('in B f1')
class C(A):
def f1(self):
print('in C f1')
class D(B,C):
def f1(self):
#super().f1()
super(C,self).f1()#super方法有参数问题:1.参数是自己本身,调用同super(),2.参数是其他的子类,按照mro顺序调用子类的上一个方法
print('in D f1')
obj=D()
print(D.mro())
obj.f1() # in A f1 in D f1
封装
- 广义:就是把属性或方法装起来,外面不能直接调用了,要通过类的名字来调用
- 狭义:把属性和方法藏起来了,外面不能掉用了,只能在内部偷偷调用
- 给一个名字前面加上了双下划线的时候,这个名字就变成了一个私有的
- 所有的私有的内容或者名字都不能在类的外部调用,只能在类的内部使用了
- 私有的内容能不能被子类使用呢?不能==>私有的各种静态变量/方法不能被子类继承
-
-
- 私有的实例变量/私有的对象属性
class User:
def __init__(self,name,passwd):
self.usr=name
self.__pwd=passwd#私有的实例变量/私有的对象属性
#defget_pwd(self):#表示的是用户不能改只能看私有+某个get方法实现的
#return self.__pwd
#def change_pwd(self):#表示用户必须调用我们自定义的修改方式来进行变量的修改私用+change方法实现
- 私有的静态变量
import hashlib
class User:
def__init__(self,name,passwd):
self.name=name
self.__passwd=passwd
def__get_md5(self):
md5=hashlib.md5()
md5.update(self.__passwd.encode('utf-8'))
return md5.hexdigest()
def getpwd(self):
return self.__get_md5()
- 加了双下划线的方法为啥不能调用了因为其会变成_User__Country
class User:
__Country='China'#'_User__Country':'China'
role='法师'#'role':'法师'
def func(self):##在类的内部使用的时候,自动的把当前这句话所在的类的名字拼在私有变量前完成变形(._User__Country)
print(self.__Country)
print(User.__dict__)
#print(User._User__Country)
ret=User()
print(ret.func())
内置函数properity
- @property #装饰这个方法不能有参数 ,将类中的方法装饰成属性
- 一个静态属性property本质就是实现了get,set,delete三种方法
properity应用场景1:
from math importpi
class Circle:
def__init__(self,r):
self.r=r
@property#把一个方法伪装成一个属性,在调用这个方法的时候不需要加()就可以直接得到返回值
def area(self):
return pi*self.r**2
property的应用场景2:和私有的属性合作的
能看不能改
class User:
def__init__(self,usr,pwd):
self.usr=usr
self.__pwd=pwd
@property
def pwd(self):
return self.__pwd
alex=User('alex','sbsbsb')
print(alex.pwd)
#property进阶(一般用的比较少)了解
class Goods:
discount=0.8
def__init__(self,name,origin_price):
self.name=name
self.__price=origin_price
@property
def price(self):
return self.__price*self.discount
@price.setter
def price(self,new_value):#你要按照我的要求来改
self.__price=10
#print('调用了',new_value)
#apple=Goods('apple',5)
#print(apple.price) #调用的是被@property装饰的price
#apple.price=10 #调用的是被setter装饰的price
#print(apple.price)
#了解
class Goods:
discount=0.8
def__init__(self,name,origin_price):
self.name=name
self.__price=origin_price
@property
def price(self):
return self.__price*self.discount
@price.setter
def price(self,new_value):
if isinstance(new_value,int):
self.__price=new_value
@price.deleter
def price(self):
del self.__price
apple=Goods('apple',5)
print(apple.price)
apple.price='ashkaksk'
del apple.price #并不能真的删除什么,只是调用对应的被@price.deleter装饰的方法而已(可以在里面操作)
print(apple.price)
反射(getattr)
- 用字符串数据类型的名字来操作这个名字对应的函数\实例变量\绑定方法\各种方法
- 反射对象的实例变量
- 反射类的静态变量/绑定方法/其他方法
- 模块中的所有变量
- 被导入的模块
- 当前执行的py文件-脚本
- 对象名.属性名===>getattr(对象名,'属性名')
对象名.属性名===>getattr(对象名,'属性名')
class Person:
def__init__(self,name,age):
self.name=name
self.age=age
def qqxing(self):
print('qqxinggggggg')
alex=Person('alex',83)
res=getattr(wusir,'name')
- 反射其他模块的内容
import a
import sys
print(getattr(sys.modules['a'],Alipay))
print(a,Alipay)#a为内存地址
- 反射本模块的内容
import sys
whaha='hahaha'
print(getattr(sys.modules['__main__'],'whaha'))
hasattr()
- hasattr()[返回是bool值]一般和getattr在一起
- callable()判断是否能够调用[返回是bool值]判断这个变量后面能不能加括号
class Person:
def__init__(self,name):
self.name=name
def func(self):
print('haha')
a=Person('haha')
print(hasattr(a,'name'))
if hasattr(a,'func'):
if callable(getattr(a,'func')):
getattr(a,'func')()
part6:
两个装饰器函数之classmethod*****
- classmethod*****(重要)被装饰的方法会成为一个类方法
- @classmethod #把一个对象绑定的方法修改成一个类方法
- 第一,在方法中仍然可以引用类中的静态变量
- 第二,可以不用实例化对象,就直接用类名在外部调用这个方法(在实例化之前使用)
- 什么时候用@classmethod?
- 定义了一个方法,默认传self,但这个self没被使用
- 并且你在这个方法里用到了当前的类名,或者你准备使用这个类的内存空间中的名字的时候
- 类方法可以通过类名调用/类方法可以通过对象名调用
案例1:
class Goods:
__discount=0.8
def__init__(self):
self.__price=5
self.price=self.__price*self.__discount
@classmethod#把一个对象绑定的方法修改成一个类方法
def change_discount(cls,new_discount): #cls指向的是Goods
print(cls,Goods)
cls.__discount=new_discount
案例2:
import time
class Date:
def__init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@classmethod
def today(cls):
struct_t=time.localtime()
date=cls(struct_t.tm_year,struct_t.tm_mon,struct_t.tm_mday)#在自己的类中做了实例化 在这里cls等同于Date
return date
date=Date.today()
print(date.year)
print(date.month)
print(date.day)
两个装饰器函数之staticmethod*
- staticmethod*被装饰的方法会成为一个静态方法(不常用)
案例1:
class User:
pass
@staticmethod
def login(a,b): #本身是一个普通的函数,被挪到类的内部执行,那么直接给这个函数添加@staticmethod装饰器就可以了
print('登录的逻辑',a,b)
#在函数的内部既不会用到self变量,也不会用到cls类
obj=User()
User.login(1,2) #可以用类名来调用
obj.login(3,4) #可以通过对象来调用
- 能定义到类中的内容
-
静态变量是个所有的对象共享的变量有对象\类调用但是不能重新赋值(对象会在自己空间里面赋值)
-
绑定方法是个自带self参数的函数由对象调用(类也可以调用,但是里面要随便写一个参数)
-
类方法@classmethod是个自带cls参数的函数由对象\类调用
-
静态方法@staticmethod是个啥都不带的普通函数由对象\类调用
-
property属性@property是个伪装成属性的方法由对象调用但不加括号
-
案例2:
class A:
country='中国'
def func(self):
print(self.__dict__)
@classmethod
def clas_func(cls):
print(cls)
@staticmethod
def stat_func():
print('普通函数')
@property
def name(self):
return 'wahaha'
__call__方法
- 对象()调用这个类中的__call__方法(看源码会用到)
- callable(对象)
- 能不能对象()调用就看这个类有没有实现__call__方法
案例1:
class A:
def__call__(self,*args,**kwargs):
print('-----------')
obj=A()
print(callable(obj))
obj()
__len__方法
- len(对象)需要实现这个类中的__len__方法
class Cls:
def__init__(self,name):
self.name=name
self.student=[]
def__len__(self):
return len(self.student)
py22=Cls('py22')
py22.student.append('小明')
py22.student.append('xiaohong')
py22.student.append('小黑')
print(len(py22))
__new__方法(构造方法)
- 1.什么时候执行2.为什么要有(创建一个对象需要的空间)3.单例模式
- 实例化的时候
- 先创建一块对象的空间,有一个指针能指向类-->new
- 调用init-->init
案例1:
class A:
def__new__(cls,*args,**kwargs):#构造方法
o=super().__new__(cls)#开空间
print('执行了new',o)
return o
def__init__(self):
print('执行了init',self)
A()
- 记忆:单例模式:(面试会考)
设计模式----单例模式(平时不用,只有在面试时会用到)
一个类从头到尾只会创建一次self空间(实例化只会开一个空间)
class Baby:
__instance=None
def__new__(cls,*args,**kwargs):
if cls.__instance is None:
cls.__instance = super().__new__(cls)
return cls.__instance
def__init__(self,cloth,pants):
self.cloth=cloth
self.pants=pants
b1=Baby('红毛衣','绿皮裤')
print(b1.cloth)
b2=Baby('白衬衫','黑豹纹')
print(b1)
print(b2)
print(b1.cloth)
print(b2.cloth)
__str__和__repr__方法
__str__(双下划线):帮助我们在打印/展示的时候,更直观的显示对象内容%sstr()print()
__repr__(双下划线):是str的备胎,同时还和%r和repr(对象)有合作关系
__str__:(只能返回str类型)打印的时候不是显示对象的内存的地址而是显示__str__里面返回的东西
在打印一个对象的时候调用__str__方法 [print(py22)]
在%s拼接一个对象的时候调用__str__方法 [print('我们py22班%s'%py22)]
在str一个对象的时候调用__str__方法 [print(str(py22))]
案例1:
class Course:
def__init__(self,name,price,period):
self.name=name
self.price=price
self.period=period
def__str__(self):
return self.name
python=Course('python',21800,'6months')
linux=Course('linux',19800,'5months')
mysql=Course('mysql',12800,'3months')
go=Course('go',15800,'4months')
print(go)
当我们打印一个对象用%s进行字符串拼接或者str(对象)总是调用这个对象的__str__方法
如果找不到__str__,就调用__repr__方法
__repr__不仅是__str__的替代品,还有自己的功能
用%r进行字符串拼接或者用repr(对象)的时候总是调用这个对象的__repr__方法
案例2:
class clas:
def__init__(self):
self.student=[]
def append(self,name):
self.student.append(name)
def__repr__(self):
return str(self.student)
def__str__(self):
return 'aaa'
py22=clas()
py22.append('大壮')
print(py22)
print(str(py22))
print('我们py22班%s'%py22)
print('我们py22班%r'%py22)#有repr就用repr,没有就不用(仍然是对象的内存地址)
print(repr(py22))