python基础-面向对象

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 <module>
    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 <module>
    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 <module>
    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-11-30 12:14  whiteList  阅读(173)  评论(0编辑  收藏  举报