• 【Python】面向对象 -没有对象?new一个吧



    重点内容:

    理解面向对象编程OOP的概念和思想方法
    掌握Python面向对象的语法规则
    理解继承, 封装, 多态等面向对象特性, 在Python中的具体体现.


    面向对象编程概念(OOP)

    面向过程是编年史: 按照时间先后顺序, 一条一条的记录发生的事情.
    面向对象是记传史: 按照每个王侯将相的维度, 记录这个人是谁, 有哪些特点, 做过哪些事情.

    所以史记其实是中国古代的一个非常成功的应用了 OOP 思想的著作

    OOP是一种编程思想. 所谓 “思想”, 指的是解决一类问题时, 使用的一系列的方法手段


    两种角色:

    • 1.类的实现
    • 2.类的调用者(使用者)

    抽象

    抽象是指对现实世界问题和实体的本质表现, 行为, 特征进行建模;抽象的反义词是 “具体”

    抽象的本质, 是抓住我们重点关注的主体, 而忽略一些我们不需要关注的细节;

    写程序也是一样, 我们不可能把一个现实事物所有的信息都在程序中表示出来, 而是只表示我们需要用到
    的.


    类和实例

    类是施工图纸, 里面有房子的重要信息(户型, 面积, 朝向, 层高等等).
    实例是造好的房子, 房子造好了, 才能住进去, 才能娶到媳妇. 如果房子烂尾了, 美好生活就只是在YY~
    通过同一张图纸可以建造出N个相同格局的房子, 那么这N个实例就都是属于同一个类(回忆下我们的type
    函数).

    image-20220319100029060


    总结:

    对象就是类的实例, 是一个客观事物的抽象.
    把多个对象按一定的规则进行归类, 就成为了类.
    Python中的对象包含三个部分: id, 类型, 值


    封装/接口

    封装描述了对数据/信息进行隐藏的观念, 对象的使用者只能通过接口来访问/修改对象的数据.
    封装的好处是减轻调用者的使用负担, 调用者只需要知道接口如何使用, 而不需要知道对象的具体实现.
    封装这个概念当然不是只存在于面向对象思想中. 回忆计算机网络协议栈的分层结构, 也是为了达到封装的效果.


    组合/包含

    多个小的类组合成一个大的类, 来解决一个相对复杂的问题.
    组合表示 has-a 语义

    组合的目的是代码重用


    派生/继承

    基于一个现有的类, 通过继承这个类创建一个新的类.

    如果基于类A用继承的方式创建了类B, 我们就将这个过程成为类A派生出了类B. 类A称为父类(或超类), 类B称为子类.子类对象包含了父类对象的所有属性和操作.

    继承表示了 is-a 这样的语义.

    继承的目的还是代码重用


    多态

    本质上讲 多态是在运行时能够自动的识别出对象的类型, 并使用对应类型的方法来完成一定的动作.
    多态也是一种 “信息隐藏”, 让调用者了解的信息越少, 那么使用起来负担就越低.
    刚才我们说的封装, 只是让调用者不知道这个类里面的实现; 而我们的多态, 是让调用者连这个类究竟是哪
    个类都不需要关心.


    自省/反射

    自省和反射是一个意思. 顾名思义, 就是认清自己是谁. 指对象在程序运行时能获取到一些自己的信息

    dir/id/type/__name__ _doc_ 这些内置属性和方法就是自省的具体体现.

    其实C++中也有typeid这样的方法来进行一部分的"自省"的功能. 只不过是和C++的设计理念背道而驰, 因
    此不推荐使用.


    属性和方法

    创建类

    使用class关键字创建一个类. 创建类的语法和创建函数很像.

    #创建一个函数
    def func():
        #文档字符串
        #函数体
        
    #创建一个类
    #默认继承object类
    class ClassName:
        #文档字符串
        #类体
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    创建一个类, 实际上是创建了一个自定制的类型. 其实Python中, 类和类型是统一的. 而不像很多其他编程
    语言那样, 区分内置类型和"类类型"

    类的定义语句一般出现在模块的顶级缩进; 但是Python并不强制要求. 类的定义也可以出现在类/函数, 或者其他语句块中. 当然作用域也就不一样了


    创建对象

    在C++或者Java中, 使用new这个关键字来创建一个对象. 在Python中把这个new直接就省略掉了

    class A():
        pass    #空语句
    a = A()
    
    • 1
    • 2
    • 3

    创建空列表时:

    #方法1:
    a = []
    #方法2:
    a = list()	#工厂函数
    
    • 1
    • 2
    • 3
    • 4

    实例方法(非静态成员函数)

    类中直接定义的函数就是类的成员函数.
    成员函数必须先创建一个类的对象, 然后通过对象的 . 操作来访问

    例子

    class C():
        def Print(self):
            print('hello')
    c = C() #创建对象
    c.Print()   #调用成员函数     hello
    #注意: c.Print() == C.Print(c)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意看, 我们在类中定义的这个成员函数Print, 有一个参数self. 这个self参数指的是这个对象本身(注意是
    对象, 不是类!).
    self相当于C++中的this指针. 这个参数不一定非叫self, 也可以改成其他的名字(比如命名为this).

    但是大家约定都写作self.


    如果我们不创建对象, 直接通过类名来调用, 就会抛出异常.

    class C():
        def Print(self):
            print('hello')
    
    C.Print()   #TypeError: Print() missing 1 required positional argument: 'self'
    
    • 1
    • 2
    • 3
    • 4
    • 5

    根据这个错误信息, 看起来貌似是缺少一个参数.我们尝试一下.

    class C():
        def Print(self):
            print('hello')
    c =C()
    C.Print(c)   	#hello
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实际上, c.Print() 在Python解释器中会转换成 C.Print© . 但是为了方便, 我们平时都写作c.Print()


    实例属性(非静态成员变量)

    类的成员变量, 也称为实例属性, 和类的属性相对应.
    Python可以在运行时给一个对象新增或删除实例属性. 这一点是C++和Java等语言做不到的.
    可以在任何一个类的方法中创建实例属性.

    class C():
        #Init需要手动调用
        def Init(self):
            self.a = 100
        def Print(self):
            print(self.a)
    c =C()#创建对象
    c.Init()#先初始化,相当于调用默认构造函数
    c.Print()#调用Print函数 100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果不先调用Init函数就会出错


    使用构造器 _init_ 来创建实例属性, _init_ 会在创建对象的时候自动被调用, 这样就不需要我们
    手动去调一个Init这样的函数了

    __init__相当于默认构造函数,通常把一个类的实例属性都写到__init__里面

    class C():
        def __init__(self):
            self.a = 100 #创建一个实例属性a的值为100
        def Print(self):
            print(self.a)
    c =C()#创建对象
    c.Print()#调用Print函数 100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    __init__ 构造器可以带一些参数, 这样的话就可以创建对象的时候直接赋值进去了. 记得第一个参数一定
    是 self(表示示例自身).

    class C():
        def __init__(self,x):
            self.a = x	#创建一个实例属性a的值为x
        def Print(self):
            print(self.a)
    c =C(200)#创建对象
    c.Print()#调用Print函数 200
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果构造器有多重不同形式的参数怎么办? 和C++, Java不同, Python不需要进行多个版本的构造器 “重载”,
    因为Python中压根就不需要 “重载” 语法~

    Python中还有一个解构器 __del__但是Python有内置的GC机制, 一般不需要程序猿手动定义__del__
    来回收对象.


    类属性(静态成员变量)

    在类创建过程中, 可以直接在类里定义一些属性

    class A():
        val = 100
    print(A.val)	#100
    
    • 1
    • 2
    • 3

    这种方法在类创建好之后就可以直接使用, 而不需要先创建一个对象. 这就是我们C++或者Java中接触过的
    静态成员变量
    所谓静态成员, 其实指的是这是一个类的属性, 而不是一个对象的属性


    静态方法(静态成员函数)

    刚才我们提到的成员函数, 都是实例属性, 必须要创建实例才能调用. 我们能否创建一个类属性, 也就是 “静态成员函数

    方法1:手动将函数改为静态成员函数 用staticmethod修改

    注意:对于静态方法, 就不需要 self 这样的参数了.,所以Print函数的参数不需要加self

    class A():
        def Print():
            print('hello')
        Print = staticmethod(Print)	#将Print变为静态成员函数
    
    A.Print()   #hello
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    方法2:

    使用 @staticmethod 这种方式来修饰 Print 函数, 同样能达将一个方法定义成静态方法的效果. 这个语法
    叫做函数修饰符, 也叫 装饰器.

    class A():
        @staticmethod
        def Print():
            print('hello')
    
    A.Print()   #hello
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    对于类的静态方法, 本质上是一个和类无关的函数, 只是这个函数的作用域是在类名之内.

    注意:静态方法不能访问静态成员变量(类属性)


    类方法

    Python中的静态方法, 其实不能和C++的静态成员函数等价. 真正能作为等价的应该是 类方法

    使用 classmethod 进行修饰, 同时使用 cls 作为第一个参数, 这样就构成了类方法.

    class A():
        x = 100 #静态成员函数->类属性
        @classmethod
        def Print(cls):
            print(cls.x)
    a = A()
    a.Print()	#100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    用cls这个参数, 来表示当前类 (记得Python中类也是对象) 刚才的self表示当前对象. 然后就可以通过 cls 来访问到类属性了

    self类似. cls 也只是一个约定俗称的名字. 也可以命名成其他的名字


    属性的访问权限

    学过C++/Java的同学们知道, 使用关键字public/private/protected等关键字, 可以对属性的访问权限进行控制. 这也是"封装" 思想的一种具体体现.

    Python中使用双下划线前缀( __ )来表示私有成员

    class A():
        def __init__(self):
            self.__a = 100  #私有成员,在类外不能访问
    a = A()
    print(a.__a)	#抛异常 AttributeError: 'A' object has no attribute '__a'
    
    • 1
    • 2
    • 3
    • 4
    • 5

    类之间的关系

    在设计一个程序时, 需要设计多个有一定关系的类. 而这种类和类之间的关系, 最主要有两种. 组合和继承

    组合

    一个类中包含其他的类, 形成这样的组合关系, 表示 has-a 语义.
    类之间显著不同, 并且较小的类是较大的类的组件的时候, 使用组合非常合适

    简单的通讯录

    例子:一个简单的通讯录,Book类包含一个标题title,和一个列表对象data(保存每一个人的信息person),同时person这个类中又包含了两个字符串,表示名字和电话

    #成员
    class person():
        #默认构造函数,self表示当前对象
        def __init__(self,name,phone):
            #成员为: name phone
            self.name = name    #姓名
            self.phone = phone  #电话
    #通讯录
    class Book():
        def __init__(self,title):
            #成员为: data列表
            self.data = []
        #添加用户
        #entry是一个person类的对象
        def AddUser(self,entry):
            self.data.append(entry)
        #展示内容
        def Show(self):
            #data是一个列表,entry是它的每一个成员
            for entry in self.data:
                print(entry.name)
                print(entry.phone)
                
    book = Book('name Phone')
    book.AddUser(person("Mango",'1'))	#添加的是一个person类对象
    book.AddUser(person("Lemon",'2'))
    book.Show()
    
    #执行结果:
    Mango
    1
    Lemon
    2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    继承

    类之间大体相似, 但是某些细节不同, 这时候适合使用继承的方式. 表示 is-a 语义.

    继承也可以理解成, 对一个已经定义好的类, 进行一定的扩展.


    如果一个类没有从任何一个父类继承, 那么可以让这个类继承自object

    class A(object):
        pass
    
    • 1
    • 2

    另外, 也可以不写继承自object. 这种类称为经典类(也叫旧式类). 旧式类和新式类大部分特性都是一样的.

    class A:	#经典类
        pass
    
    • 1
    • 2

    子类能够继承父类的属性和方法.

    class Parent:
        def ParentPrint(self):
            print('ParentPrint')
    class Child(Parent):    #表示继承Parent这个类
        def ChildPrint(self):
            print('ChildPrint')
    p = Parent()    #创建父类对象
    p.ParentPrint() #父类对象调用父类的方法
    c = Child() #创建子类对象
    c.ParentPrint()#子类对象调用父类的方法
    c.ChildPrint()#子类对象调用子类的方法
    #执行结果:
    ParentPrint
    ParentPrint
    ChildPrint
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如果子类中存在和父类中相同名字的方法, 子类方法会覆盖父类方法.*Python的覆盖和方法的参数没关系

    class Parent:
        def func(self):
            print('ParentPrint')
    class Child(Parent):    #表示继承Parent这个类
        def func(self):
            print('ChildPrint')
    p = Parent()    #创建父类对象
    p.func() #父类对象调用父类的方法
    c = Child() #创建子类对象
    c.func()    
    #执行结果
    ParentPrint
    ChildPrint
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这一点在实现类的构造器要额外注意. 子类和父类都有同名的构造器. 子类的构造器中需要显式调用父类构
    造器.

    class Parent:
       def __init__(self):
           self.x = 10
    class Child(Parent):    #表示继承Parent这个类
        def __init__(self):
            self.y = 20
    p = Parent()    #创建父类对象
    print(p.x)
    c = Child() #创建子类对象
    print(c.x)	#抛异常AttributeError: Child' object has no attribute 'x'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    没有显式调用父类构造器, 执行报错.


    如何显示调用?在子类的__init__函数中,加上: Parent.__init__(self) #显示调用父类的方法

    class Parent:
       def __init__(self):
           self.x = 10
    class Child(Parent):    #表示继承Parent这个类
        def __init__(self):
            Parent.__init__(self) #显示调用父类的方法
            self.y = 20
    p = Parent()    #创建父类对象
    print(p.x)
    c = Child() #创建子类对象
    print(c.x)	
    #执行结果:
    10
    10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以使用super方法来协助我们找到父类的方法, 而不需要显式的指定父类的类名.

    在子类的__init__函数中,加上: super(子类的名称,self).__init__() #显示调用父类的方法

    class Parent:
       def __init__(self):
           self.x = 10
    class Child(Parent):    #表示继承Parent这个类
        def __init__(self):
            super(Child,self).__init__()	#显示调用父类的方法
            self.y = 20
    p = Parent()    #创建父类对象
    print(p.x)
    c = Child() #创建子类对象
    print(c.x)	
    #执行结果:
    10
    10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    理解Python的多态

    对于Python的多态, 其实和 “父类/子类” 并没有必然联系.

    对于Python的这种多态方式, 也称为 鸭子类型(duck typing). 只要一个东西, 能像鸭子一样能叫, 能走路,
    能游泳, 那么就可以认为这是一只鸭子~~而不考虑这个对象真实是什么类型.

    例如

    def Add(x,y):
        return  x + y
    print(Add(1,2))
    print(Add("hello","world"))
    print(Add([1,2],[3,4]))
    #执行结果
    3
    helloworld
    [1, 2, 3, 4]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    好了, 其实鸭子类型, 并不是Python这样的动态类型语言的专属. 一些静态类型语言

    类和实例的常用内建函数

    issubclass

    判定一个类是不是另外一个类的子类.

    class Parent:
        pass
    class Child(Parent):    #表示继承Parent这个类
        pass
    
    print(issubclass(Child,Parent)) #True
    --------------------------------------------------
    class Parent:
        pass
    class Child():
        pass
    
    print(issubclass(Child,Parent)) #False
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    isinstance

    判定一个对象是不是一个类的实例.

    class C:
        pass
    c = C()
    print(isinstance(c,C))  #True
    
    • 1
    • 2
    • 3
    • 4

    注意:isinstance还可以判断一个对象的类型

    a = 10
    print(isinstance(a,int))	#True
    
    • 1
    • 2

    hasattr, getattr, setattr, delattr

    用来对一个类/对象的属性进行操作和判定

    image-20220323205434558

    class C:
        x = 100
    print(hasattr(C,'x'))   #True
    print(hasattr(C,'y'))   #False
    
    • 1
    • 2
    • 3
    • 4

    image-20220323205541051

    class C:
        x = 100
    print(getattr(C,'x'))   # 100
    print(getattr(C,'y'))   #类C中没有y属性,抛异常:AttributeError: type object 'C' has no attribute 'y'
    
    • 1
    • 2
    • 3
    • 4

    image-20220323205516679

    该函数没有返回值,所以如果打印返回值是None

    class C:
        x = 100
    print(setattr(C,'x',200))	#None
    print(C.x)  #200
    setattr(C,'y',600)
    print(C.y)  #600
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    image-20220323205531341

    class C:
        x = 100
    delattr(C,'x')	#x这个属性被删除
    print(C.x)	#AttributeError: type object 'C' has no attribute 'x'
    
    • 1
    • 2
    • 3
    • 4

    dir

    查看类/对象的所有属性和方法.

    class C:
        pass
    print(dir(C))
    #执行结果
    ['__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__']
    
    • 1
    • 2
    • 3
    • 4
    • 5

    vars

    和dir类似. dir只是返回了有哪些属性, 但是没显示属性的值. vars将值也取了出来.

    class C:
        pass
    print(vars(C))
    #执行结果: 
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'C' objects>, '__weakref__': <attribute '__weakref__' of 'C' objects>, '__doc__': None}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    super

    获取到当前类/对象的父类
    super本质也是一个工厂函数, 创建了一个父类的对象


    特殊方法的作用

    我们前面学的 init 只是其中一个特殊方法. 其实类还有很多特殊方法, 通过这些特殊方法, 可以完成

    _str_

    class Time:
        def __init__(self,hour,min):
            self.hour = hour
            self.min = min
        def __str__(self):
            return f"{self.hour}:{self.min}"
    t = Time(21,11)
    print(t)	#21:11
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果不实现_str_,那就是打印对象的id

    <_main_.Time object at 0x0000027FAECC3400>


    _len_

    如:直接获取字符串的长度作为时间类的长度

    class Time:
        def __init__(self,hour,min):
            self.hour = hour
            self.min = min
        def __str__(self):
            return f"{self.hour}:{self.min}"
        def __len__(self):
            return len(self.__str__())
    t = Time(21,11)
    print(t)	#21:11
    print(len(t))	#5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    特殊方法一览

    image-20220319101801978

    image-20220319101807323

    image-20220319101814462

    image-20220319101822352

    image-20220319101827118

  • 相关阅读:
    面试官问我TCP三次握手和四次挥手,我真的是
    Linux 环境搭建
    面试:Android页面改色方案
    redhat/centos 配置本地yum源
    国际慈善日 | 追寻大爱无疆,拓世科技集团的公益之路
    UE 材质,如何只取0~1之间的值,其余值抛弃
    leetcode0207 链表相交
    使用 SAP ABAP 代码生成 PDF 文件,填充以业务数据并显示在 SAPGUI 里
    ssm社区线上超市购物系统毕业设计-附源码211704
    AVL平衡二叉树
  • 原文地址:https://blog.csdn.net/chuxinchangcun/article/details/126652242