10、面向对象

2022/7/23 6:22:47

本文主要是介绍10、面向对象,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

10.1面向对象思想

面向过程:

核心是“过程”二字

过程的终极奥义就是将程序流程化

过程就是“流水线”,分步骤来解决问题

面向对象:

核心是“对象”二字

对象的终极奥义就是将程序整合 对象是“容器”,用来盛放数据与功能的程序=数据+功能

一种数据和功能整合起来就是面向对象思想编程列表也可看做对象

10.2类与对象

什么是类?

类也是容器,该容器用来存放 同类对象 共有的数据与功能

# 在程序中先定义类,在调用类产生对象
# 一、先定义类
# 类式对象相似数据与功能的集合体
# 所以,类体中常见的代码是变量和函数的定义,但是类体其实是可以包含任意其他代码的
# 注意:类体代码是在类定义阶段就会立即执行,会产生类的名称空间
# class Student:
#     # 1.变量的定义
#     stu_school='oldboy'
#
#     # 2.功能的定义
#     def tell_stu_info(stu_obj):
#         print('学生信息:名字%s 年龄:%s 性别: %s' %(
#             stu_obj['stu_name'],
#             stu_obj['stu_age'],
#             stu_obj['stu_gender']
#         ))
    # print('=====>')

# 属性访问的语法
# print(Student.__dict__) # 查看内存空间里面的东西
# print(Student.__dict__['stu_school'])
# 访问数据属性
# print(Student.stu_school)  # 与上面的功能一样,但是写起来要简单些
# 访问函数属性
# print(Student.tell_stu_info)

# 二、调用类产生对象
# stu1=Student()
# stu2=Student()

# 代码太多
# stu1.stu_name='andy'
# stu1.stu_age=12
# print(stu1.__dict__)
#
# stu2.stu_name='lili'
# stu2.stu_age=18
# print(stu2.__dict__)

# 解决方法1:
# def init(obj,x,y,z):
#     obj.stu_name=x
#     obj.stu_age=y
#     obj.stu_gender=z
# init(stu1,'andy',18,'男')
# init(stu2,'lili',12,'女')
# print(stu1.__dict__)
# print(stu2.__dict__)

# 解决方法2:
class Student:
    # 1.变量的定义
    stu_school='oldboy'

    def __init__(obj, x, y, z):
        obj.stu_name = x
        obj.stu_age = y
        obj.stu_gender = z

    # 2.功能的定义
    def tell_stu_info(stu_obj):
        print('学生信息:名字%s 年龄:%s 性别: %s' %(
            stu_obj['stu_name'],
            stu_obj['stu_age'],
            stu_obj['stu_gender']
        ))

# 调用类的过程又称为实例化
# 过程:
# 1、先产生一个空对象
# 2、然后自动调用__init__方法,将空对象已经调用类时括号内传入的参数一同传入给__init__方法
# 3、返回初始化完的对象
stu1=Student('andy',12,'男')
stu2=Student('lili',18,'女')
print(stu1.__dict__)
print(stu2.__dict__)

# 总结init方法
# 1、会在调用类时自动触发执行,用来为对象初始化自己独有的数据
# 2、__init__ 内应该存放为对象初始化属性的功能,但是可以存放任意其他代码,想要在类调用时就立刻执行的代码都可以放到该方法内
# 3、__init__方法必须返回None,不用写return None,默认就是返回None,没有返回值


属性查找

类有两种属性:

1、类的数据属性

类的数据属性是所有对象共享的

2、类的函数属性

类的函数属性是绑定给对象用的

class Student:
    # 1.变量的定义
    stu_school='oldboy'

    def __init__(obj, x, y, z):
        obj.stu_name = x
        obj.stu_age = y
        obj.stu_gender = z

    # 2.功能的定义
    def tell_stu_info(stu_obj):
        print('学生信息:名字%s   年龄:%s    性别:%s' %(
            stu_obj.stu_name,
            stu_obj.stu_age,
            stu_obj.stu_gender
        ))

    def choose(self,x):
        print('正在选课')
        self.course=x

# 类总存放的是对象共有的数据与功能
# 类可以访问到数据属性和函数属性
# 类中的数据是共享给所有的对象用的,访问的地址是一样的
stu1=Student('andy',12,'男')
stu2=Student('lili',18,'女')

# print(stu1.__dict__)
# print(stu1.stu_age)
# print(Student.stu_school)
# stu1.stu_school='xxx'
# print(stu1.stu_school)

# 类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,
# 但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同

# 类调用自己的函数属性必须严格按照函数的用法来
# Student.tell_stu_info(stu1)
# Student.tell_stu_info(stu2)

# 绑定方法的特殊之处在于:谁来调用绑定方法就会将谁当做第一个参数自动传入,不用传入参数
# print(Student.tell_stu_info)
# print(stu1.tell_stu_info)
# 在类中新增一个函数,要传入一个参数 规范使用self

# stu1.tell_stu_info()
# stu2.tell_stu_info()

# 第一个参数不用传
stu1.choose('Python全站开发')
print(stu1.course)

# l=[1,2]
# print(type(l))  # <class 'list'>

10.3封装

面向对象编程有三大特性:封装、继承、多态,其中最重要的一个特性就是封装。

1、什么是封装

封装是面向对象三大特性最核心的思想

封装指的就是把数据与功能都整合到一起

封装就是整合

2、将封装的属性进行隐藏操作

如何隐藏:双下划线开头

 class Foo:
     __x=1 # '_Foo__x'
     def __f1(self): # '_Foo__f1'
          print('from test')


    def f2(self):
         print(self.__x)
         print(self.__f1)

 print(Foo.__dict__)
 print(Foo._Foo__x)  # 可以访问  注意1

 这种变形只在类定义里面发生
 obj = Foo()
 obj.f2() # 注意2

注意:

1、在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。

2、对内不对外, 在类内部是可以直接访问双下滑线开头的属性的 。

3、 变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。

为何要隐藏属性?

1、隐藏数据属性

将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制

class People:
    def __init__(self,name):
        self.__name = name

    def get_name(self):
        # 别人想用可以间接的访问这个接口来访问
        print(self.__name)

    def set_name(self,val):
        # 也可以通过接口去修改内容
        self.__name=val


obj = People('upup')
obj.get_name()
obj.set_name('UPUP')
obj.get_name()

2、隐藏函数属性

隐藏函数的目的是隔离复杂度, 例如ATM程序的取款功能,该功能有很多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,而对使用者来说,只需要开发取款这个功能接口即可,其余功能我们都可以隐藏起来

class ATM:
    def __card(self): #插卡
        print('插卡')
    def __auth(self): #身份认证
        print('用户认证')
    def __input(self): #输入金额
        print('输入取款金额')
    def __print_bill(self): #打印小票
        print('打印账单')
    def __take_money(self): #取钱
        print('取款')
    def withdraw(self): #取款功能
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

obj=ATM()
obj.withdraw()

property装饰器

property,可以将类中的函数“伪装成”对象的数据属性

class People:
    def __init__(self,name,weight,height):
        self.name = name
        self.weight = weight
        self.height = height
# 
#     定义函数的原因
#     通过计算得到的,应该是触发功能计算的
#     bmi是随着身高和体重的数据而变化的,不是一个固定的值
#     但是,bmi更像是一个数据属性,而不是功能
#     这个时候就可以加一个property属性,绑定给对象的一个方法伪装成数据属性
    @property
    def bmi(self):
        return self.weight / (self.height ** 2)

obj1 = People('upup',60,1.67)
# print(obj1.bmi())
print(obj1.bmi)

property其他知识

当隐藏数据后,需要开接口供别人使用,可以使用以下方式

class People:
    def __init__(self,name):
        self.__name = name

    def get_name(self):
        return self.__name


    def set_name(self,val):
        if type(val) is not str:
            print('必须传入str类型')
            return
        self.__name = val

    def del_name(self):
        print('不能删除')

    name=property(get_name,set_name,del_name)


obj1 = People('upup')
# print(obj1.get_name())
# obj1.get_name()
# obj1.del_name()
print(obj1.name)
obj1.name='up'  #修改数据
print(obj1.name) 

修改后:

class People:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self): # get_name 改为name
        return self.__name

    @name.setter
    def name(self,val): # set_name 改为name
        if type(val) is not str:
            print('必须传入str类型')
            return
        self.__name = val

    @name.deleter
    def name(self):
        print('不能删除')

    name=property(get_name,set_name,del_name)


obj1 = People('upup')
# print(obj1.get_name())
# obj1.get_name()
# obj1.del_name()
print(obj1.name)
obj1.name='up'  #修改数据
print(obj1.name)

10.4继承

1、什么叫继承

继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,新建的类可称为子类或派生类,父类又可称为基类或超类

注意:支持多继承

多继承:

优点:子类可以同时遗传多个父类的属性,最大限度的重用代码

缺点:继承表达的是一种什么是什么的关系,可能会跨种类继承,可读性会变差,扩展性变差,不建议使用多继承,如果真的涉及到多继承方面,应该使用Mixins机制

2、为什么要使用继承

可以解决类与类之间代码冗余问题

类时来解决对象与对象之间的问题

class Parent1(object): # 定义父类1
    x = 1

class Parent2(object): # 定义父类2
    pass

class Sub1(Parent1): # 单继承
    pass

class Sub2(Parent1,Parent2):  # 多继承
    pass

# print(Sub1.__bases__)  # (<class '__main__.Parent1'>,)
# print(Sub2.__bases__)  # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

# 在Python2中有经典类和新式类之分
# 新式类:继承了object类的子类,以及该子类的子类
# 经典类:没有继承了object类的子类,以及该子类的子类

# 在python3中,没有继承任何类,那么会默认继承object类,在Python3中所有的类都是新式类
# print(Parent1.__bases__) # (<class 'object'>,)
# print(Parent2.__bases__) # (<class 'object'>,)

# print(Sub1.x)

3、如何实现继承

要找出类与类之间的继承关系,需要先抽象,再继承。

# 类与类之间存在冗余问题,比如有相似的schoo
# class Student:
#     school='OLDBOY'
#
#     def __init__(self,name,age,sex):
#         self.name=name
#         self.age=age
#         self.sex=sex
#
#     def choose_course(self):
#         print('学生%s 正在选课' %self.name)
#
#
# class Teacher:
#     school='OLDBOY'
#
#     def __init__(self,name,age,sex,salary,level):
#         self.name=name
#         self.age=age
#         self.sex=sex
#         self.salary=salary
#         self.level=level
#
#     def score(self):
#         print('老师 %s 正在给学生打分' %self.name)


使用继承的方法后:

class OldboyPeople:
    school = 'OLDBOY'
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex


class Student(OldboyPeople):
    # school='OLDBOY'

    # def __init__(self,name,age,sex):
    #     self.name=name
    #     self.age=age
    #     self.sex=sex

    def choose_course(self):
        print('学生%s 正在选课' %self.name)

stu_obj = Student('lili',18,'female')
print(stu_obj.__dict__)
stu_obj.choose_course()

class Teacher(OldboyPeople):
    # school='OLDBOY'

    def __init__(self,name,age,sex,salary,level):
        # 在父类中只用到了一半的功能,还有自身的功能没有用上
        OldboyPeople.__init__(self,name,age,sex)
        self.salary=salary
        self.level=level

    def score(self):
        print('老师 %s 正在给学生打分' %self.name)

tea_obj = Teacher('andy',12,'male',1200,2)
print(tea_obj.salary)
print(tea_obj.__dict__)

属性查找

单继承背景下的属性查找

对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找……

class Foo:
    def f1(self):
        print('Foo.f1')


    def f2(self):
        print('Foo.f2')
        self.f1()


class Bar(Foo):
    def f1(self):
        print('Bar.f1')

obj=Bar()
obj.f2()


# Foo.f2
# Bar.f1

obj.f2()会在父类Foo中找到f2,先打印Foo.f2,然后执行到self.f1(),即obj.f1(),仍会按照:对象本身->类Bar->父类Foo的顺序依次找下去,在类Bar中找到f1,因而打印结果为Foo.f1

父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的

class Foo:
    def __f1(self): # _Foo__f1
        print('Foo.f1')


    def f2(self):
        print('Foo.f2')
        self.__f1() # self.__Foo_f1


class Bar(Foo):
    def __f1(self): # _Bar__f1
        print('Bar.f1')

obj=Bar()
obj.f2()

继承的实现原理

菱形问题

一个子类是可以同时继承多个父类的,这就带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的 Diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),菱形其实就是对下面这种继承结构的形象比喻
用代码来表示:

class A:
    pass

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B,C):
    pass

python到底是如何实现继承的呢?

定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表

class A:
    pass

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B,C):
    pass

# 对于定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表
print(B.mro())
obj1 = D()
obj1.test()

# [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
# from B

非菱形继承下,Python2余Python3的属性查找,都是一样,都是一个分支一个分支的找下去,最后找object

class E:
    def test(self):
        print('from E')


class F:
    def test(self):
        print('from F')


class B(E):
    def test(self):
        print('from B')


class C(F):
    def test(self):
        print('from C')


class D:
    def test(self):
        print('from D')


class A(B, C, D):
    # def test(self):
    #     print('from A')
    pass


print(A.mro())
'''
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]
'''

obj = A()
obj.test()
# 先找到A,如果A没有就会在B中找,如果B没有,就会到B继承的E中找,依次类推,直到找到位置。

如果多继承是菱形类,经典类与新式类的属性查找顺序不一样;

经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑

新式类:广度优先,会在检索最后一条分支的时候检索最后一条总的类

深度优先

class G: # 在python2中,未继承object的类及其子类,都是经典类
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->G->C->F->D->object
# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试

广度优先

class G(object):
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
# 可依次注释上述类中的方法test来进行验证

总结:

多继承可以使用,但是继承结果尽量不要过于复杂,要满足继承的什么是什么的关系。

mixins机制

1、用来解决多继承的问题的

2、mixins机制的核心:就是在多继承背景下尽可能提升多继承的可读性

class Vehicle:
    pass


class FlyableMixin:  # 给这个类添加一个Mixin的后缀名,表示这是给其他类额外添加的一个功能
    # 过于飞行有关的都可以放在这里面
    def fly(self):
        pass

class CivilAircraft(FlyableMixin,Vehicle): # 民航飞机
    pass

class Helicopter(FlyableMixin,Vehicle):  # 直升飞机
    pass

class car(Vehicle):  # 汽车 汽车与飞机还是有不一样的,汽车没有飞的功能
    pass

注意:

1、 如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin

2、 子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能

3、 它不依赖于子类的实现

4、 它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀

派生和方法重用

在子类派生的新方法中如何重用父类的功能

方式一:

指名道姓的调用每一个类下的函数,不依赖于继承关系

class OldboyPeople:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

    def f1(self):
        print('%s say hello' %self.name)


class Teacher(OldboyPeople):
    def __init__(self,name,age,sex,level,salsry):
        OldboyPeople.__init__(self,name,age,sex)

        self.level = level
        self.salary = salsry

tea_obj=Teacher('andy',12,'male',10,3000)
print(tea_obj.__dict__)

方式二:

super()调用父类提供给自己的方法,严格依赖继承关系

调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找

class OldboyPeople:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

    def f1(self):
        print('%s say hello' %self.name)


class Teacher(OldboyPeople):
    def __init__(self,name,age,sex,level,salsry):
        super().__init__(name,age,sex)  # python3中科院简写
        # super(Teacher,self).__init__(name,age,sex)  Python2要写全

        self.level = level
        self.salary = salsry

tea_obj=Teacher('andy',12,'male',10,3000)
print(tea_obj.__dict__)
# 调用super会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro,去当前类的父类中去找属性
class A:
    def test(self):
        print('from A')
        super().test()

class B:
    def test(self):
        print('from B')

class C(A,B):
    pass

obj = C()
obj.test()

# from A
# from B

# print(C.mro())  # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

注意:以上的两种方法都可以使用,但是不要混着使用

组合

在一个类中以另外一个类的对象作为数据属性,称为类的组合。

10.5多态

1、什么是多态?

多态指的是一类事物有多种形态,比如动物有多种形态:猫、狗、猪

2、为什么要有多态?

多态性指的是可以在不考虑对象具体的情况下,而直接去使用对象

class Animail:
    def say(self):
        print('我会:',end='')

class People(Animail):
    def say(self):
        super(People, self).say()
        print('呜呜呜')

class Dog(Animail):
    def say(self):
        super(Dog, self).say()
        print('汪汪汪')

class Pig(Animail):
    def say(self):
        super(Pig, self).say()
        print('昂昂昂')

obj1=People()
obj2=Dog()
obj3=Pig()

# obj1.say()
# obj2.say()
# obj3.say()

# 也可以定义一个统一的接口
def talk(animal):
    animal.say()

talk(obj1)

# 内置了一个__len__()的方法,这也是多态的一种体现
# print('hello'.__len__())
# print([1,2,3].__len__())

# def my_len(val):
#     return val.__len__()
#
# print(my_len('hi'))

其实完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing):“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度

#二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系
class Txt: #Txt类有两个与文件类型同名的方法,即read和write
    def read(self):
        pass
    def write(self):
        pass

class Disk: #Disk类也有两个与文件类型同名的方法:read和write
    def read(self):
        pass
    def write(self):
        pass

绑定方法和非绑定方法:

绑定方法

@classmethod

1> 绑定给对象的方法:调用者是对象,自动传入的是对象

2> 绑定给类的方法:调用者是类,自动传入的是类

# 配置文件settings.py的内容
ip='127.0.0.1'
port=3306

# 类方法的应用
import settings
class Mysql:
    def __init__(self,ip,port):
        self.ip = ip
        self.port = port

    def func(self):
        print('%s   %s' %(self.ip,self.port))


    @classmethod  # 将下面的函数装饰成绑定给类的方法
    def from_conf(cls):
        return cls(settings.ip,settings.port)


obj1=Mysql('12',8080)
obj1.func()

非绑定方法 静态方法

@ staticmethod

没有绑定给任何人,调用者可以是类,可以是对象,没有自动传参的效果

class Mysql:
    def __init__(self,ip,port):
        self.nid =self.create_id()
        self.ip = ip
        self.port = port

# 函数体不需要对象也不需要类,就可以使用静态方法
    @staticmethod   # 将下述函数装饰成一个静态方法
    def create_id(x,y):
        print(x,y)
        import uuid
        return uuid.uuid1()
obj1 = Mysql('12',8080)
obj1.create_id(1,2)

10.6内置函数

# 绝对值
print(abs(-12))

# 布尔值
# 全部为真才会返回True。可迭代对象为空,也返回True
print(all('1'))  # True
print(all([1,'aaa','1','']))   #  False
print(all([]))  # True

# 全部是假就会返回假,有一个是真的就会返回True,可迭代对象为空,返回False
print(any([]))  # False
print(any([0,None,1]))  # True

# 进制转换
print(bin(11))
print(oct(11))
print(hex(11))

# 判断某一个变量对应的值可不可以被调用
def foo():
    pass

class Foo:
    pass

print(callable(Foo))  # True
print(callable(foo))  # True

# ASCLL 值
print(chr(65))
print(ord('a'))

# 不可变集合
s = frozenset({1,2,3})

# 10 ** 2 % 3
print(pow(10,2,3))


*** # 能够进行属性查找
class Foo:
    pass

print(dir(Foo))


***#  得到索引和对应的值
for i,v in enumerate(['q','v','c']):
    print(i,v)

***# 可以将余数和商放到一起
print(divmod(20,4))  # (5, 0)

***# 执行字符串中的表达式
res= eval('1+2')
print(res)  # 3

***# 判读一个对象是不是一个类的实例,类型判断
class Foo:
    pass

obj = Foo()
print(isinstance(obj,Foo))  # True

***# 拉链函数
v1 = 'hello'
v2 = [111,222,333,444,555]
res = zip(v1,v2)
print(list(res))  #  [('h', 111), ('e', 222), ('l', 333), ('l', 444), ('o', 555)]

***# 如果文件里面有一堆的模块,需要导入不能使用import time
__import__('time')
x.sleep(3)




这篇关于10、面向对象的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程