第七章 面向对象
7.1.1 基本概念
- 类 : 具有相同方法和属性的一类事物
- 对象、实例 : 一个拥有具体属性值和动作的具体个体,是类的一个实例
- 实例化 :从一个类得到一个具体对象的过程
7.1.2 基本格式
# 定义类 class 类名: #类名 第一个是大写字母 def 方法名(self,name): #必须由self print(name) return 123 def 方法名(self,name): print(name) return 123 def 方法名(self,name): print(name) return 123 # 调用类中的方法 # 1.创建该类的对象 obj = 类名() # 2.通过对象调用方法 result = obj.方法名('alex') print(result)
7.1.3 应用场景
- 应用场景:遇到很多函数,需要给函数进行归类和划分
- 三个词:类、对象、方法
- 补充:
- 类和对象是什么关系?
- 对象是类的一个实例
- self是什么?(obj.方法时会把obj传给self)
- self就是一个形式参数,对象调用方法时,python内部会将该对象传给这个参数
- 类内部代码会从上到下依次执行,类变量立即执行,方法需调用内部代码才会执行
- 类与类之间可以嵌套,执行遵循上条
- 类/方法/对象 都可以当做变量嵌套到其他类型中
- 什么时候用面向对象?
- 函数(业务功能)比较多,可以使用面向对象来进行归类
- 想要做数据封装(创建字典存储数据时,面向对象)
- 游戏示例:创建一些角色并且根据角色需要再创建人物
- 类和对象是什么关系?
7.2 面向对象的三大特性
7.2.1 封装
对象(obj)的作用:存储一些值,方便以后使用
class File: def read(self): with open(self.name,mode='r',encoding='ut-8')as f: data =f.read() return data def write(self,content): with open(self.name,mode='a',encoding='ut-8')as f: f.write(content) obj1=File() #创建了一个对象obj1,(实例化对象) obj1.name ="data.txt" #在对象中写了一个name = ‘data.txt’ obj1.read()#通过对象类中的方法,read 方法中的self就是obj1 obj2 = File() obj2.name ='info.txt' obj2.write('alex')
封装的作用:将数据封装到对象,方便使用 更专业的写法
class Person: def __init__(self,n,a,g): #初始化方法-给对象内部数据初始化 self.name = n #谁调用show,self就是谁(p1/p2) self.age = a self.gender = g def show(self): tmp ='我是%s,年龄%s,性别%s' %(self.name,self.age,self.gender) print(tmp) #实例化对象 p1 =Person('zz',19,'男') #当Person() 时 __init__就会执行 p1.show() p2 =Person('zy',22,'男') p2.show() #我是zz,年龄19,性别男 我是zy,年龄22,性别男
总结:如果写代码时,函数比较多比较乱。
- 可以将函数/方法归类并放到同一个类中
- 函数如果有一个反复使用的公共值,则可以放到对象中
7.2.1.1 你理解的封装?
(2个,一个是封装一些方法到一个类,另一个是封装一些数据,等之后方便调用)
- 函数封装到类
- 数据封装到对象
# 示例:循环让用户输入:用户名/密码/邮箱。 输入完成后再进行数据打印。 # 以前的写法: USER_LIST = [] while True: user = input('请输入用户名:') pwd = input('请输入密码:') email = input('请输入邮箱:') temp = {'username':user,'password':pwd,'email':email} USER_LIST.append(temp) for item in USER_LIST: temp = "我的名字:%s,密码:%s,邮箱%s" %(item['username'],item['password'],item['email'],) print(temp) # 面向对象写法一: class Person: def __init__(self,user,pwd,email): self.username = user self.password = pwd self.email = email USER_LIST = [对象(用户/密码/邮箱),对象(用户/密码/邮箱),对象(用户/密码/邮箱)] while True: user = input('请输入用户名:') pwd = input('请输入密码:') email = input('请输入邮箱:') p = Person(user,pwd,email) USER_LIST.append(p) for item in USER_LIST: temp = "我的名字:%s,密码:%s,邮箱%s" %(item.username,item.password,item.email,) print(temp) # 面向对象写法二: class Person: def __init__(self,user,pwd,email): self.username = user self.password = pwd self.email = email def info(self): return "我的名字:%s,密码:%s,邮箱%s" %(item.username,item.password,item.email,) USER_LIST = [对象(用户/密码/邮箱),对象(用户/密码/邮箱),对象(用户/密码/邮箱)] while True: user = input('请输入用户名:') pwd = input('请输入密码:') email = input('请输入邮箱:') p = Person(user,pwd,email) USER_LIST.append(p) for item in USER_LIST: msg = item.info() print(msg)
7.22 继承
- 所有的查找名字(调用方法和属性)都是先找自己的,自己没有找父类
- 如果自己和父类都有func,希望自己和父类都调用,super()/指定类名直接调 (在obj对象继承关系范围内找下一个类的func,多继承Base(Foo,Bar))从左到右
class F3(object): def f1(self): ret = super().f1() #这里执行F2里的f1方法 print(ret) return 123 class F2(object): def f1(self): print('123') class F1(F3, F2): pass obj = F1() obj.f1() #123 #None
#父类 class Base: def f1(self): pass #子类 class Foo(Base): #这里就是Foo继承Base,Base是Foo的父类 def f2(self): pass #创建了一个子类的对象 obj = Foo() #执行对象.方法时,优先在自己的类找,如果没有就到父类中查找 obj.f1() obj.f2() #只有子类能继承父类,父类不能继承子类
#父类 class Base: def f1(self): pass #子类 class Foo(Base): #这里就是Foo继承Base,Base是Foo的父类 def f2(self): pass class Bar(base): def f3(self): pass obj1 = Foo() obj2 = Bar() obj1.f1()
继承关系中的查找顺序:
#示例1: class Base: def f1(self): print('base.f1') class Foo(Base): def f2(self): print('foo.f2') obj = Foo() obj.f1() #base.f1 obj.f2() #foo.f2 #示例2: class Base: def f1(self): print('base.f1') class Foo(Base): def f2(self): self.f1() print('foo.f2') def f1(self): print('foo.f1') obj = Foo() #注意self指的是obj,f1现在自己类中找,如果没有趣父类找 obj.f2() #foo.f1 #foo.f2 #示例3: class Base: def f1(self): self.f2() print('base.f1') def f2(self): print('base.f2') class Foo(Base): def f2(self): print('foo.f2') obj = Foo() obj.f1() #foo.f2 #base.f1 #示例5 #多继承 (里面的self一直是obj,obj是ThreadingTCPServer创建的,所以找的时候从ThreadingTCPServer类找) class BaseServer: def serve_forever(self, poll_interval=0.5): self._handle_request_noblock() def _handle_request_noblock(self): self.process_request(request, client_address) def process_request(self, request, client_address): pass class TCPServer(BaseServer): pass class ThreadingMixIn: def process_request(self, request, client_address): pass class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass obj = ThreadingTCPServer() obj.serve_forever()
注意事项:
问题:什么时候才能用到继承?多个类中如果有公共方法,可以放到基类中,避免重写。
- self到底是谁?
- self是哪个类创建的(一开始创建的对象obj),就从此类开始找,没有就从父类找
- 多继承关系:多继承,如果一个派生类有多个基类,执行对象.方法时,优先在自己的类中找,如果没有,就从左到右依次从基类中找 要注意self是谁?
7.23 多态(多种形态/多种类型) 也叫鸭子模型
一个类表现出的多种状态-->多个类表现出相似的状态
1.基本内容:多种形态/多种类型,python本身就是多态的
def func(arg): #可以多种类型,很多事物 arg.send()# 但必须由一定的规则,send方法。
2.面试题:
对于一个函数而言,python对于参数的类型不会限制,那么传入参数时就可以是各种类型,在函数中如果有arg.send方法,那么就是对于传入类型的一个限制(类型必须有send方法),这就是鸭子模型。
类似于上述的函数我们认为只要能呱呱叫的就是鸭子(只有有send方法,就是我们想要的类型)
7.3 成员
7.3.1 类成员
定义class类时,会在内存中开辟一块空间,但里面的方法/函数不会执行,只有被调用时才会执行
1.类变量:city (静态字段/属性) name在obj对象里
定义:写在类的下一级,与方法同级
访问:
类.类变量名称 obj1.name 对象.类变量名称
面试题
class Base: x =1 #实例化对象 obj = Base() obj.x #先去对象中找,如果没有在去类中找 obj.y = 123 #在对象中添加了一个y =123变量。 obj.x = 123 #在对象中添加了一个y =123变量。 print(obj.y) #123 print(obj.x) #123 #只能在自己对象中添加,不会修改类变量中的x print(Base.x) #1
总结:
找变量时优先找自己,自己没有去父类中查找
这里要的是Child.x 自己没有去父类找,结果为1 。因为一个类可能有很多对象,所以没办法去对象里找
修改变量时,只能修改自己成员的变量。obj.x =123 ,修改的只是obj对象中的x,对类变量中的Base.x无影响
- 绑定方法/普通方法
- 定义:至少有一个self参数
- 执行: 先创建对象,由对象.方法()
class Foo: def __init__(self): self.name = 123 def func(self,a,b): print(self.name,a,b) obj = Foo() #标准的,创建了对象,也用到了 obj.func(1,2)
3.静态方法
@staticmethod装饰器
参数无限制
执行:
- 类.方法() 直接调用
- 对象.方法() (不推荐)
- 类.方法() 直接调用
什么时候用静态方法?
- 不用__init__里面封装的一些值
class Foo: def __init__(self): self.name = 123 def func(self,a,b): print(self.name,a,b) @staticmethod def f1(): print(1234) obj = Foo() #标准的,创建了对象,也用到了 obj.func(1,2) Foo.f1() #直接类.方法 调用静态方法,不用创建对象,节省内存
4.类方法
定义: (相比静态方法只是多了一个默认参数cls)
- @classmethod装饰器
- 至少有一个cls参数,传的是当前类
执行:
- 类.方法()
- 对象.方法() (不推荐)
class Foo: def __init__(self): self.name = 123 def func(self,a,b): print(self.name,a,b) @staticmethod def f1(): print(1234) @classmethod def f2(cls,a,b): print('cls输出的是当前的类',cls) print('a,b') obj = Foo() #标准的,创建了对象,也用到了 obj.func(1,2) Foo.f1() #直接类.方法 调用静态方法,不用创建对象,节省内存 Foo.f2(1,2)
面试题:
- 问: @classmethod和@staticmethod的区别? - 答:一个是类方法,一个静态方法 定义: 类方法:用@classmethod做装饰器且至少有一个cls参数 静态方法:用staticmethod做装饰器且参数无限制 调用: 类.方法直接调用,对象.方法也可以调用
5.属性
定义:
@property装饰器
至少有一个self参数
执行:对象.方法 不用加括号了
class Foo: @property def func(self): print(123) return 666 obj = Foo() result = obj.func #这里调用func方法不用加括号 print(result)
#属性应用 # 属性的应用 class Page: def __init__(self, total_count, current_page, per_page_count=10): self.total_count = total_count self.per_page_count = per_page_count self.current_page = current_page @property def start_index(self): return (self.current_page - 1) * self.per_page_count @property def end_index(self): return self.current_page * self.per_page_count USER_LIST = [] for i in range(321): USER_LIST.append('alex-%s' % (i,)) # 请实现分页展示: current_page = int(input('请输入要查看的页码:')) p = Page(321, current_page) data_list = USER_LIST[p.start_index:p.end_index] for item in data_list: print(item)
7.3.2 实例(对象)成员
对象里的是实例变量 (字段、属性)
- 就是创建对象obj后,创建的变量name = ‘alex’,name为实例变量
对象的嵌套:
- 函数:参数可以是任意类型
- 字典:对象和类都能当字典的key和value
- 一个类的对象作为另一个类对象的实例变量 (site.registry(3,UserConfig))
class School(object): def __init__(self,title,addr): self.title = title self.address = addr class ClassRoom(): def __init__(self,name,school_object): self.name =name self.school = school_object #这里接受School的对象 s1 = School('北京','沙河') s2 = School('上海','浦东') s3 = School('深圳','南山') c1 = ClassRoom('全栈21',s1) cl.name c1.school.title c1.school.address #这里就都可以调用了
class StackConfig(object): list_display = '李邵奇' def changelist_view(self): print(self.list_display) class UserConfig(StackConfig): list_display = '利奇航' class AdminSite(object): def __init__(self): self._register = {} def registry(self,key,arg=StackConfig): self._register[key] = arg def run(self): for key,value in self._register.items(): obj = value() obj.changelist_view() site = AdminSite() site.registry(1) site.registry(2,StackConfig) site.registry(3,UserConfig) site.run()
7.3.3 成员修饰符
1、公有,所有地方都能访问
2、私有,只有自己才能访问,修饰符: 双下划线__ #加了修饰符后 class 类变量和实例变量都只能在内部访问
#示例1 #在外部不能访问,在类内部可以 class Foo: def __init__(self,name): self.__name = name def func(self) print(self.__name) obj = Foo('alex') #print(obj.__name) #报错,无法访问 obj.func() #可以访问 #示例2 class Foo: __x = 1 @staticmethod def func(): print(Foo.__x) #报错 Foo.func() #示例3 class Foo: def __fun(self): print('msg') def show(self): self.__fun() obj = Foo() #obj,__func() #报错 obj.show() #keyi
3、强制访问私有,中间加上_类名 (单下划线)
class Foo(): def __init__(self,name): self.__x = name obj = Foo('alex') print(obj._Foo__x) #强制访问私有实例变量
补充:
class Foo(): pass class Foo(object): pass #在python3中 这两种写法一样,因为所有类都会继承object类,由他开辟内存,全部都是新式类 #如果在python2中定义,称为经典类 class Foo: pass #如果在python2中这样定义,称为新式类 class Foo(object): pass
7.3.4 特殊成员
7.3.4.1 __init__
- 初始化方法
- 用途:用于给对象obj中赋值
class Foo(): def __init__(self,name): self.name = name obj = Foo('alex') #在执行Foo() 后,就会执行init方法初始化值
7.3.4.2 __new__
- 构造方法
- 用于创建空对象 在object对象中有
class Foo(object): def __init__(self): print('初始化对象') self.x = 123 def __new__(cls, *args, **kwargs): print('创建对象') return object.__new__(cls) obj = Foo() # 先执行__new__方法,再执行__init__方法
7.3.4.3 __call__
- 对象后面加括号 执行call方法 。按理来说对象后面不能加()
class Foo(object): def __call__(self, *args, **kwargs): print('执行call方法') obj = Foo() obj() # 对象后面加括号,执行__call__方法 # 相当于Foo()()
- 网站示例 (建站的原理)
from wsgiref.simple_server import make_server def func(environ,start_response): start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')]) return ['你好'.encode("utf-8") ] class Foo(object): def __call__(self, environ,start_response): start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')]) return ['你<h1 style="color:red;">不好</h1>'.encode("utf-8")] # 作用:写一个网站,用户只要来方法,就自动找到第三个参数并执行。 server = make_server('127.0.0.1', 8000, Foo()) server.serve_forever()
7.3.4.4 __getitem__
__setitem
delitem
class Foo(object): def __setitem__(self, key, value): pass def __getitem__(self, item): return 'uuu' def __delitem__(self, key): pass obj1 = Foo() obj1['k1'] = 123 # 内部会自动调用 __setitem__方法 val = obj1['xxx'] # 内部会自动调用 __getitem__方法,用val接受返回值 del obj1['ttt'] # 内部会自动调用 __delitem__ 方法
7.3.4.5 __str__
- 只有在打印对象时,会自动调用此方法,并将其返回值在页面显示出来
class Foo(object): def __str__(self): return 'asdfasudfasdfsad' obj = Foo() print(obj) # obj是一个对象,只在打印的时候调用__str__方法,并返回值
- 示例:
class User(object): def __init__(self,name,email): self.name = name self.email = email def __str__(self): return "%s %s" %(self.name,self.email,) user_list = [User('二狗','2g@qq.com'),User('二蛋','2d@qq.com'),User('狗蛋','xx@qq.com')] for item in user_list: print(item)
7.3.4.6 __dict__
- 去对象中找到所有变量并将其转换为字典
class Foo(object): def __init__(self,name,age,email): self.name = name self.age = age obj = Foo('alex',19) val = obj.__dict__ # 去对象中找到所有变量并将其转换为字典 print(val)
7.3.4.7 __enter__ __exit__
- 上下文管理
# 面试题 class Foo(object): def do_something(self): print('内部执行') # 第三步 class Context: def __enter__(self): print('进入') # 第一步 return Foo() def __exit__(self, exc_type, exc_val, exc_tb): print('推出') # 第四步 with Context() as ctx: #这里的ctx 就是接受return的返回值 print('内部执行') # 第二步 ctx.do_something()
7.3.4.8 __add__
- 两个对象相加时用到此方法
class Foo(object): def __add__(self, other): # self:obj1,other:obj2 return 123 obj1 = Foo() obj2 = Foo() val = obj1 + obj2 # 内部会自动调用 __add__方法,并用val接受返回值 print(val) # 123
7.3.4.9 __iter__
- 将一个对象变成可迭代对象时用到此方法
class Foo: def __iter__(self): return iter([1,2,3,4]) obj = Foo()
7.4 数据结构
7.4.1 队列
- 先进先出 fifo
class Queue(object): ''' 先进先出 ''' def __init__(self): self.data_list = [] def push(self,val): self.data_list.insert(0,val) def pop(self): return self.data_list.pop(0)
7.4.2 栈
- 后进先出 lifo
class Stack(object): ''' 后进先出 ''' def __init__(self): self.data_list = [] def push(self,val): ''' 向栈中压入一个数据(入栈) :param val: :return: ''' self.data_list.append(val) def pop(self): ''' 从栈中拿走一个数据(出栈) :return: ''' return self.data_list.pop()
7.5 约束
- 又名:Foo抽象类、接口类
- 定义:就是给子类一个规范,让子类必须按照抽象类的规范来实现方法
- 示例
#NotmplementedError #约束子类中必须写send方法,如果不写,则调用的时候回主动抛出异常 class BaseMessage(object): def send(self,a1): raise NotmplementedError #(看到这里就潜规则的发现子类中需要有send方法) class Msg(BaseMessage): def senf(self): pass class Email(BassMessage): def send(self): pass class DingDing(BaseMessage):# 写新的方法时,内部必须由send方法 def send(self): pass obj = Email() obj.send()
7.6 反射
- 根绝字符串的形式去某个对象中操作他的成员 (这里的对象可以是类,模块,py文件)
- python中一切皆为对象(模块,包,类,对象)
7.6.1 getattr (对象,字符串)
- 根据字符串的形式去某个对象中获取该成员
class Foo(object): def __init__(self,name): self.name = name def login(self): print('登录') obj = Foo('alex') #获取变量 v1 =getattr(obj,'name') print(v1) #获取方法 method_name = getattr(obj,'login') #这里就相当于obj.login print(method_name()) #执行 #alex #登录 #None
- 补充
class Foo(object): def get(self): pass obj = Foo() # if hasattr(obj,'post'): # getattr(obj,'post') v1 = getattr(obj,'get',None) # 推荐 print(v1)
7.6.2 hasattr (对象,字符串)
- 根据字符串的形式去判断是否有该成员
from wsgiref.simple_server import make_server class View(object): def login(self): return '登陆' def logout(self): return '登出' def index(self): return '首页' def func(environ,start_response): start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')]) obj = View() # 获取用户输入的URL method_name = environ.get('PATH_INFO').strip('/') if not hasatt(obj.method_name): #不是成员就返回下面 return ["sdf".encode("utf-8"),] response = getattr(obj,method_name)() #有就执行 return [response.encode("utf-8") ] # 作用:写一个网站,用户只要来方法,就自动找到第三个参数并执行。 server = make_server('192.168.12.87', 8000, func) server.serve_forever()
7.6.3 setattr (对象,字符串,值)
- 根据字符串的形式去某个对象中设置/修改该成员
class Foo(object): pass obj = Foo() obj.k1 = 999 setattr(obj,'k1',123) print(obj.k1) #123
7.6.4 delattr (对象,字符串)
- 根据字符串的形式去某个对象中删除该成员
class Foo: pass obj = Foo() obj.k1 = 999 delattr(obj,'k1') print(obj.k1)
7.6.5 python一切皆对象
- 对象的定义
- 狭义:类实例化出来的就是对象
- 广义:
- 模块(py文件)及其内部成员
- 包(文件夹)及其内部成员
- 类及其内部成员
- 狭义的对象
- python一切皆对象,所以以后想要通过字符串的形式操作其内部成员都可以通过反射的机制实现
7.7 单例模式(设计模式)
1.单例模式(23中设计模式)
#多例模式 每实例化一次就会创建一个新的对象 class Foo(): pass obj1 = Foo() obj2 = Foo() #单例模式 无论实例化多少次,都会用上一次创建的那个对象 obj1 = Foo() obj2 = Foo()
- 单例模式标准 Singleton
class Singleton(object): #单例模式 instance = None def __new__(cls, *args, **kwargs): # if Singleton.instance: # return Singleton.instance # Singleton.instance = object.__new__(cls) # return Singleton.instance if not cls.instance: cls.instance = object.__new__(cls) return cls.instance obj1 = Singleton() obj2 = Singleton() #不是最终的,要加锁 多线程
- 应用示例 : 文件连接池,数据库连接池
class FileHelper(object): instance = None def __init__(self, path): self.file_object = open(path,mode='r',encoding='utf-8') def __new__(cls, *args, **kwargs): if not cls.instance: cls.instance = object.__new__(cls) return cls.instance obj1 = FileHelper('x') obj2 = FileHelper('x')
- 模块导入
- 多次导入重新加载
import jd #第一次会加载全部一遍 import jd #已经有就不会再加载了 print(456) #如果想重新加载 import importlib importlib.reload(jd) #会重新加载
- 通过模块导入也能实现单例模式
#jd.py class Foo(object): pass obj = Foo() #app.py import jd #加载jd.py,加载后会实例化一个Foo对象给obj print(jd.obj) #在有其他也许导入jd时,用的还是之前创建的那个对象
7.8 项目结果目录
7.8.1 项目分类
脚本:把所有代码写在一个py文件中
单可执行文件:只有一个可执行文件
# 可执行文件中包含: if __name__ == '__main__': pass
多可执行文件:有多个可执行文件
7.8.2 项目目录
- 包及其内含文件示例
- 可执行文件:bin
- 将所有可执行文件放入其中 #注意要把项目路径加到sys.path中
- 业务相关:src
- 登陆注册相关:account.py
- 订单相关:order.py
- 主程序:run.py
- 公共类库:lib
- 分页功能
- 数据库:db
- 用户资料:user.txt
- 配置:config
- 设置:setting.py
- 日志存储:log
- 所有的日志文件
- 可执行文件:bin
注意:每个可执行文件必须把项目根目录加入sys.path中
import os import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR)