1.面向对象
面向对象编程是在面向过程编程的基础上发展来的,它比面向过程编程具有更强的灵活性和扩展性,所以可以先了解下什么是面向过程编程:
面向过程编程的核心是过程,就是分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,接着依次调用即可,再简单理解就是程序
从上到下一步步执行,从头到尾的解决问题;
而面向对象编程是把构成事物的整个需求按照特点、功能划分,将这些存在共性的部分封装成对象,创建对象的目的不是为了完成某一个步骤,
而是为了描述某个事物在整个解决问题的步骤中的行为。
eg: 小明用美的洗衣机洗脏衣服,流程是怎样的?
面向过程的解决方法:
1、执行加洗衣液方法;
2、执行开启洗衣机方法;
3、执行加水方法;
4、执行洗衣服方法;
5、执行甩干方法;
6、取出衣服;
以上就是将解决这个问题的过程拆成一个个方法,通过按顺序执行方法来解决问题。
面向对象的解决方法:
1、可以先归纳出两个对象:“美的洗衣机”对象和“小明”对象
2、针对对象“美的洗衣机”加入一些它的方法:“自动注水方法”“洗衣方法”、“烘干方法”
3、针对对象“小明”加入他的方法:“加洗衣液方法”、“开启洗衣机方法”、“取出衣服方法”
4、然后执行,使用对象.动作 的方式,执行各步骤
小明.加洗衣液
小明.开启洗衣机
美的洗衣机.自动注水
美的洗衣机.洗衣服
美的洗衣机.烘干
小明.取出衣服
解决同一个问题 ,面向对象编程就是先抽象出对象,然后用对象执行方法的方式解决问题。
2.类
如果想通过面向对象编程,首先需要创建一个类(class),才能实例化(或叫具象化)对象;
(洗衣例子中要先有人这个类,才能有“小明”对象、先有洗衣机类,才能有“美的洗衣机”这个对象)
类可以理解成一个模板,通过它可以创建出无数个具体实例(对象);
使用类的关键字class,来声明类,首字母大写,多个单词时每个单词首字母要大写(驼峰命名法);
eg: class MyName(object):
(object)可以不写,object是python中的一个通用对象,添加它后可以使用更多的内置功能;
class Test(object): name = 'test' # 定义一个类属性 def run(self): print(f'{self.name}在跑步!')xiaoming = Test() # 实例化一个对象xiaomingprint(xiaoming.name) # 对象xiaoming可以调用类属性xiaoming.run() # 对象xiaoming可以调用类方法'''testtest在跑步!'''
类的参数self
可以看到类的方法中默认第一个参数是self,且是必填的;(python中的self关键字只用于类的方法中);
self也是一个对象,它代表实例化变量(例子中的xiaoming)本身(xiaoming可以调用name属性和run方法,都是self帮助找到的)
class Person(object): name = None # 类属性(也叫类实例化属性) age = None def run(self): print('可以直接使用self调用类属性') print('打印属性:'+str(self.name)) a = 'new' # 类方法中定义的变量无self时,属于方法中的局部变量 print('打印局部变量:'+a) def work(self): print('利用self可直接调用类中其它类函数') self.run() def jump(): print('不添加self参数,就不属于类函数,就不可以被实例化的对象调用')# 实例化一个对象xiaomingxiaoming = Person()# 可以直接调用类属性(类实例化属性)print(xiaoming.name) # None# 也可为类属性(类实例化属性)重新赋值xiaoming.name = 'xiaoming'# 调用类方法, 调用时无需传递self参数值xiaoming.work()'''利用self调用类中其它函数可以直接使用self调用类属性打印属性:xiaoming (此时类属性值已被实例化对象修改)打印局部变量:new'''# print(xiaoming.a) # AttributeError: 'Person' object has no attribute 'a' 方法中的局部变量不可被实例化对象调用# 实例化另一个对象xiaohongxiaohong = Person()print(xiaohong.name) # None # 对象xiaohong并不会使用到对象xiaoming修改的类属性(类实例化属性)值;对象修改的类属性(类实例化属性),只能作用于对象本身xiaohong.jump() # 非类函数无法被对象调用,直接报错'''Traceback (most recent call last): File "D:\python_exercise\test_calss.py", line 44, in xiaohong.jump()TypeError: Person.jump() takes 0 positional arguments but 1 was given'''# 可以看到报错信息是函数无参数值,却被传递了一个,说明对象在调用方法时,自动传递了self参数,所以直接报错了
3.类的构造函数
类中的一种默认函数,用来将类实例化的同时,将参数传入类中;(类似于函数执行时,可以传一些参数)
def __init__(self, a, b):
self.a = a # 类实例化对象的属性
self.b = b
此时self.a和self.b就可以在类的各个类函数中使用了;
class Person(object): def __init__(self, name): self.name = name # self.声明的变量是类实例属性 def run(self): print(f"{self.name}在跑")test = Person('x')test.run() # x在跑'''此时已经见过了类中可定义的多种变量类下直接定义的变量,属于类属性、又属于类实例化对象属性 (可被实例化后的对象直接引用)构造函数中self.开头定义的属性,属于类实例化对象属性,不属于类属性(可被实例化后的对象直接引用)(工作中多用,且多在构造函数中提前定义好)类函数中a=''定义的变量,属于局部变量,既不属于类实例化对象属性,也不属于类属性(不可被实例化后的对象直接引用)'''
4.对象的生命周期
一个对象从出生到消亡的过程;
实例化对象后,会调用内置函数__init__, 此时对象生命开始,该对象会被内存分配一个内存块;
对象不再使用类中的方法属性时、或整个脚本结束执行时,对象会自动调用内置函数__del__通知内存管家,从内存中释放占用的内存块,对象生命结束;
无论是数字、字符串、列表、元组等对象,生命周期皆是如此;
python中会让对象自动调用__del__的操作,无需在程序中书写;
__def__所有这种书写形式的方法,都是类的内置函数,定义类时书写object(class Name(object)),就可以调用这些内置函数了。
5.私有函数和私有变量
私有:独有的、不公开;
无法被实例化后的对象调用的类中的函数和变量,就是私有函数、私有变量;
类的内部可以在类函数中调用私有函数和私有变量;
使用场景:某一方法只希望内部业务调用使用,不希望被使用者调用;
定义方法:在类函数、类变量前添加__ (两个下划线);
class Cat(object): def __init__(self, name): self.name = name self.__sex = 'boy' # 私有类实例化属性 def run(self): # 类函数可以调用私有函数 self.__run(self.__sex) # 私有函数 def __run(self, sex): # 私有函数可以使用类实例化属性 print(f'{self.name},是个{sex},它在跑!')cat = Cat('ll')cat.run() # ll,是个boy,它在跑!# 对象不可调用私有函数,直接报错# cat.__run() # AttributeError: 'Cat' object has no attribute '__run'# 也有可调用的方法,但既然创建了私有函数、私有变量,建议遵守使用规则print(dir(cat)) # 打印所有类的内置函数,就可以看到私有函数的调用名了'''['_Cat__run', '_Cat__sex', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'run']'''cat._Cat__run('girl') # ll,是个girl,它在跑!print(cat._Cat__sex) # boy
6.python中封装
python中封装的概念:
我们在类中把某些属性和方法隐藏起来、定义为私有,只能在类的内部使用,外部无法访问,或者留下少量的接口(函数)供外部访问,就是封装的概念;
这样做的目的是为了保护隐私、明确区分内外。
7.类的装饰器
装饰器也是一种函数;
它可以接收函数作为参数;且可以返回一个函数;
接收一个函数,内部对其进行处理,然后返回一个新函数,动态的增强函数功能;
# 简单梳理下装饰器的大概由来# 有一个业务函数print_test()def print_test(info): print('业务函数'+info)'''此时想在执行业务函数前后增加日志输出,且有多个类似的业务函数需要完成同样的操作,可以编写另一个公用函数,以业务函数为参数,业务函数前后完成相应日志输出'''def add_info(func, info): print('开始的日志') func(info) print('结束的日志')# 执行add_infoadd_info(print_test, 'test')'''开始的日志业务函数test结束的日志'''# 但是这样改变了原有的print_test(info)完成业务操作的写法,可以借助装饰器写法优化下def add_info_new(func): def wrapper(*args, **kwargs): print('打印开始的日志') func(*args, **kwargs) print('打印结束的日志') return wrapperprint_test = add_info_new(print_test)print_test('test')'''打印开始的日志业务函数test打印结束的日志'''# python中可以借助@语法糖,优化上面print_test = add_info_new(print_test)的写法@add_info_newdef print_test_final(info): print('借助语法糖的业务函数'+info)print_test_final('test')'''打印开始的日志借助语法糖的业务函数test打印结束的日志'''# 装饰器调用时,也可以传递参数,对业务处理进行再次的判断def add_print_args(handle): def decorator(func): def wrapper(*args, **kwargs): print('依旧执行前打印日志') func(*args, **kwargs) print('依旧执行后打印日志') if handle: print('额外处理') return wrapper return decorator@add_print_args(handle=True)def print_test_args(info): print('打印一下'+info)print_test_args('test')'''依旧执行前打印日志打印一下test依旧执行后打印日志额外处理''''''使用装饰器后,被装饰函数的元信息会被修改,例如__name__, doc等'''print(print_test_args.__name__)# wrapper (这里也很好理解,已经被返回成新的装饰函数了)'''若想保存元信息不变,可以使用wrap库,from functools import wrapswraps也是一个装饰器,它是把原函数元信息拷贝到了装饰器函数中'''from functools import wrapsdef add_print_wrap(func): @wraps(func) def wrapper(*args, **kwargs): print('执行前打印') func(*args, **kwargs) print('执行后打印') return wrapper@add_print_wrapdef print_test_wrap(info): print('业务函数'+info)print_test_wrap('test')print(print_test_wrap.__name__)'''执行前打印业务函数test执行后打印print_test_wrap''''''类装饰器通过内置函数__call__处理额外操作'''class AddPrint(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print('开始的打印foo') self.func(*args, **kwargs) print('结束的打印foo')@AddPrintdef print_test_foo(info): print('业务函数'+info)print_test_foo('test')'''开始的打印foo业务函数test结束的打印foo''''''类装饰器也可以通过传参做额外操作'''class Foo(object): def __init__(self, handle): self.handle = handle def __call__(self, func): def wrapper(*args, **kwargs): print('执行前操作') func(*args, **kwargs) print('执行后操作') if self.handle: print('额外操作') return wrapper@Foo(handle=True)def print_test5(info): print('业务操作'+info)print_test5('test')'''执行前操作业务操作test执行后操作额外操作'''
8.几种内置的常见装饰器
@classmethod
将类的函数定义为可以不经过实例化而直接被调用;此时可以不实例化,直接调用该方法;
class Foo(object): def __init__(self, a): self.a = a def run(self): print('run') @classmethod def jump(cls): print('jump') # 此时self被替换成cls,代表类本身# Foo.run()'''Traceback (most recent call last): File "D:\python_exercise\test5.py", line 21, in Foo.run()TypeError: Foo.run() missing 1 required positional argument: 'self'因为正常我们实例化的时候,python会自动帮我们将self参数传递进去此时没有实例化,所以报错没有传self参数'''Foo.jump() # jump# 类函数就可以不用实例化直接调用了
cls函数不可引用self函数
class Foo(object): def __init__(self, a): self.a = a def run(self): print('run') @classmethod def jump(cls): print('jump') cls.run()Foo.jump()'''TypeError: Foo.run() missing 1 required positional argument: 'self''''
self函数可以引用cls函数
class Foo(object): def __init__(self, a): self.a = a def run(self): print('run') self.jump() @classmethod def jump(cls): print('jump')foo = Foo('test')foo.run()foo.jump() # 实例化对象也可使用cls函数'''runjumpjump'''
@staticmethod
将类函数定义为可以不经过实例化而直接被调用,且该函数不需要传递self或cls,且无法在该函数内调用其它类函数或类变量;
class Foo(object): def __init__(self): pass def run(self): print('run') self.jump() @staticmethod def jump(): print('jump')Foo.jump() # jumpfoo = Foo()foo.jump() # jump (也可以通过对象调用)foo.run() # static函数也可以被其它类函数调用'''runjump'''
@property
将类函数的调用免去括弧,类似于调用属性;
class Foo(object): @property def run(self): print('run')foo = Foo()foo.run # run
同样使用这种调用方法,需要传参时,有自己的写法;
class Foo(object): @property def run(self): print('run') @run.setter def run(self, info): print(info)foo = Foo()foo.run = 'test info' # test info
9.类的继承
通过继承,子类可以拥有父类所有的属性和方法;
父类不具备子类自有的属性和方法;
定义方法 class Child(Parent): Child类继承Parent类;
class Parent(object): def __init__(self, name, age): self.name = name self.age = age def walk(self): print(f'{self.name}在行走')class Child(Parent): def run(self): self.walk() # 子类可以调用父类的方法 print(f"{self.name}在跑步") # 子类可以直接调用父类的属性child = Child('ll', 13) # 继承父类后,父类的初始化参数子类也要传递child.walk() # ll在行走 (子类实例化对象可以调用父类的方法)child.run()'''ll在行走ll在跑步'''
10.类的多态
子类继承父类后,对于父类中的同一功能可以表现出多状态变化(多种执行方式、结果等),且是通过子类对父类方法的重写实现的;
class Parent(object): def __init__(self, name): self.name = name def walk(self): print('父类在行走')class Child(Parent): def walk(self): print('子类在行走')child = Child('儿子')child.walk() # 子类在行走'''为什么要继承为了使用已经写好的类中的方法为什么要多态为了保留子类中和父类名称相同的函数的功能'''
11.python中的super函数
在子类重新书写父类方法时,此时想既保留父类方法的逻辑、同时增加新逻辑,就可以借助super函数;
用法:super(当前类,self(类的实例)).父类的方法();python3.0时代,super()中两个参数可以省略;
class Parent(object): def __init__(self, name): self.name = name print('父类构造函数'+self.name)class Child(Parent): def __init__(self, name, age): super(Child, self).__init__(name) self.age = age print('子类新的构造函数'+str(self.age))child = Child('xiaoming', 23)'''父类构造函数xiaoming子类新的构造函数23'''# 例子中子类对于父类中的构造函数参数进行了扩充,在工作中很常用
12.类的多重继承
子类可以继承多个父类;
class Child(Parent, Parent2, Parent3…):
class Father(object): def run(self): print('父亲跑') def walk(self): print('父亲走')class Mother(object): def run(self): print('母亲跑') def sing(self): print('母亲唱')class Child(Father, Mother): passchild = Child()child.sing() # 母亲唱child.run() # 父亲跑 (多个父类有重名方法时,优先继承写在第一位的类)print(Child.__mro__) # (, , , )'''__mro__ 方法可以打印类的继承链'''
13.类的几个高级函数
__str__ 返回类的描述信息;
class Test(object): passtest = Test()print(test) # class Test2(object): def __str__(self): return 'this is a test class'test2 = Test2()print(test2) # this is a test class
__getattr__ 当调用的属性或方法不存在时,会返回该方法定义的信息;
class Test(object): passtest = Test()print(test.a) # 调用类的属性不存在时,会直接报错'''Traceback (most recent call last): File "D:\python_exercise\test7.py", line 14, in print(test.a)AttributeError: 'Test' object has no attribute 'a''''class Test2(object): def __getattr__(self, item): return f'{item}不存在'test2 = Test2()print(test2.a) # a不存在
__setattr__ 拦截当前类中不存在的属性和值,并做处理;
class Test(object): def __init__(self, name, age): self.name = name self.age = age def __setattr__(self, key, value): print(key, value) # 打印所有的属性字典 print(self.__dict__) self.__dict__[key] = value print(self.__dict__)test = Test('xiao', 32)test.sex = 'boy'print(test.sex)'''name xiao{}{'name': 'xiao'}age 32{'name': 'xiao'}{'name': 'xiao', 'age': 32}sex boy{'name': 'xiao', 'age': 32}{'name': 'xiao', 'age': 32, 'sex': 'boy'}boy'''# 可以看到每一次生成新的属性,都会调用__setattr__方法,无论是在构造函数,还是在实例对象test.sex = 'boy'test.name = 'new_xiaoming'# 也可修改原属性的值'''{'name': 'new_xiaoming', 'age': 32, 'sex': 'boy'}'''
__call__ 将实例化对象直接变成函数使用;
class Test(object): def __call__(self, *args, **kwargs): print(f'call 函数开始: {args[0]}')test = Test()test('test') # call 函数开始: test
# eg: 编写一个可以通过 对象.a.b.c() 执行的类class Test(object): def __init__(self, args=''): print('------') self.args = args def __getattr__(self, item): print('开始的item:'+item) print('self.args:'+self.args) if self.args: item = f'{self.args}.{item}' print('后来的item:'+item) return Test(item) def __call__(self, *args, **kwargs): print('ttttt') test = Test()test.a.b.c() # ttttt'''------开始的item:aself.args:后来的item:a------开始的item:bself.args:a后来的item:a.b------开始的item:cself.args:a.b后来的item:a.b.c------ttttt'''
总结