面向对象编程编程的系统总结——转自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

posted @ 2022-12-02 11:15  小杨的冥想课  阅读(56)  评论(0编辑  收藏  举报