面向对象编程编程的系统总结——转自white-list
刚在浏览博客时看到了white-list对面向对象总结
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() # 实例化一个对象xiaoming
print(xiaoming.name) # 对象xiaoming可以调用类属性
xiaoming.run() # 对象xiaoming可以调用类方法
'''
test
test在跑步!
'''
类的参数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参数,就不属于类函数,就不可以被实例化的对象调用')
实例化一个对象xiaoming
xiaoming = 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' 方法中的局部变量不可被实例化对象调用
实例化另一个对象xiaohong
xiaohong = 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_info
add_info(print_test, 'test')
'''
开始的日志
业务函数test
结束的日志
'''
但是这样改变了原有的print_test(info)完成业务操作的写法,可以借助装饰器写法优化下
def add_info_new(func):
def wrapper(args, **kwargs):
print('打印开始的日志')
func(args, **kwargs)
print('打印结束的日志')
return wrapper
print_test = add_info_new(print_test)
print_test('test')
'''
打印开始的日志
业务函数test
打印结束的日志
'''
python中可以借助@语法糖,优化上面print_test = add_info_new(print_test)的写法
@add_info_new
def 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 wraps
wraps也是一个装饰器,它是把原函数元信息拷贝到了装饰器函数中
'''
from functools import wraps
def add_print_wrap(func):
@wraps(func)
def wrapper(args, **kwargs):
print('执行前打印')
func(args, **kwargs)
print('执行后打印')
return wrapper
@add_print_wrap
def 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')
@AddPrint
def 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函数
'''
run
jump
jump
'''
@staticmethod
将类函数定义为可以不经过实例化而直接被调用,且该函数不需要传递self或cls,且无法在该函数内调用其它类函数或类变量;
class Foo(object):
def __init__(self):
pass
def run(self):
print('run')
self.jump()
@staticmethod
def jump():
print('jump')
Foo.jump() # jump
foo = Foo()
foo.jump() # jump (也可以通过对象调用)
foo.run() # static函数也可以被其它类函数调用
'''
run
jump
'''
@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):
pass
child = Child()
child.sing() # 母亲唱
child.run() # 父亲跑 (多个父类有重名方法时,优先继承写在第一位的类)
print(Child.mro) # (<class 'main.Child'>, <class 'main.Father'>, <class 'main.Mother'>, <class 'object'>)
'''
mro 方法可以打印类的继承链
'''
13.类的几个高级函数
str 返回类的描述信息;
class Test(object):
pass
test = Test()
print(test) # <main.Test object at 0x00000298AC9264D0>
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):
pass
test = 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:a
self.args:
后来的item:a
开始的item:b
self.args:a
后来的item:a.b
开始的item:c
self.args:a.b
后来的item:a.b.c
ttttt