标签: 编程

  • Python 面向对象

    简介

    对具象化的事物进行抽象, 尽管细节有偏差, 但与其他大部分编程语言的面向对象概念类似.

    一个极简实例

    class Animal(object):
    
        def __init__(self, name, color):
            self.name = name
            self.color = color
    
        def describe(self):
            # return self.name + self.score
            print(f'名字:{self.name} | 颜色:{ self.color}')
    
    
    # 注意到__init__方法的第一个参数永远是self,表示创建的实例本身,
    # 因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
    # 构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值
    xialing = Animal('黠灵', '灰白')
    xialing.describe()
    
    # 名字:黠灵 | 颜色:灰白
    # Process finished with exit code 0

    魔术方法 __init__

    class Human(object):
        def describe(self):
            pass
    
    xialing = Human()
    xialing.name= "黠灵"
    xialing.ability= "飞翔"
    
    huban = Human()
    xialing.name= "虎斑"
    xialing.ability= "瞬移"
    
    ...
    ...
    ...

     

    • 这其实是一个不是问题的问题,  初始化, 在其他语言和很多程序的设计中, 都会使用到.
    • 比如人, 有些属性是需要通过学习和机遇才能获得, 想象科学家和普通人的区别.
    • 有些属性则是人类天生就有的共性, 比如一出生就拥有了机体, 会睡觉, 会摄取营养.
    • 很显然, 作为实例/个体的人, 有许多共性, 都具有姓名, 年龄, 族别, 性别等.  如果为每一个实例去一个个添加这些共同的属性, 会非常麻烦.
    class Human(object):
        def __init__(self, name, age, gender, nationality):
            self.name = name
            self.age = age
            self.gender = gender
            self.nationality = nationality
    
        def describe(self):
            pass
    
    xialing = Human("黠灵", "6", "男", "汉") # 此时xialing 便具有所有的初始属性
    print(xialing.name, xialing.age, xialing.gender, xialing.nationality )

    望文生义 # initialization / initiate, 初始化,是一种特殊的方法。在创建对象时, 自动初始化对象,不需要手动/显式地调用. 即为对象成员变量赋初始值.

     

    魔术方法 __new__

    __init__ 的功能很明显, 其内部如何实现功能, 则与__new__相关.

    Python 中实例化对象时, 不需要显式地使用new关键字 (其他语言比如PHP中则需要).

    但实际上在实例化对象时, Python会自动效用__new__方法

     

    class Animal(object):
        def __init__(self):
            print('init方法被执行')
    
    
        def __new__(cls, *args, **kwargs):
            print('new方法被执行')
            
            print(cls)                    # cls 是 Animal 类        
            print(object.__new__(cls) )   # 得到 Animal 类的对象     
        
            return super().__new__(cls)   # return object.__new__(cls) 这样写也行
    
    
    xialing = Animal()
    
    # new方法被执行
    # init方法被执行
    # Process finished with exit code 0

    __new__ 先执行

    __init__ 后执行

     

    self 是什么?

    class Person(object):
        def ability(self):
            print(self)
            print('self的内存地址:' + str(id(self)))
    
    laozi = Person()
    laozi.ability()
    print("实例化得到的对象的内存地址:" + str(id(laozi)))
    
    zhuangzi = Person()
    zhuangzi.ability()
    print("实例化得到的对象的内存地址:" + str(id(zhuangzi)))
    
    
    # <__main__.Person object at 0x00000233A6D5D750>
    # self的内存地址:2420865619792
    # 实例化得到的对象的内存地址:2420865619792
    #
    # <__main__.Person object at 0x00000233A6D5D7D0>
    # self的内存地址:2420865619920
    # 实例化得到的对象的内存地址:2420865619920
    #
    # Process finished with exit code 0

     

    由此可知:

    • self 和 当前对象指向相同的内存地址
    • self 是 对象的引用

     

    析构方法 __del__

    # 当一个对象被删除或销毁时, Python 解释器会默认调用__del__()方法, 它称之为析构方法.
    
    class Animal(object):
        def __init__(self, name):
            self.name = name
            print("__init__ 方法被调用")
    
    
        def __del__(self):
            print("__del__ 方法被调用")
            print(f'{self.name}对象被销毁')
    
    cat = Animal("kitty")
    # print(input("请输入"))
    
    # __init__ 方法被调用
    # __del__ 方法被调用
    # kitty对象被销毁
    # Process finished with exit code 0

     

    继承

    单继承: 继承的最简实例

    class Animal(object):  # 创建一个基类
        def run(self):
            print('这是Animal基类内部的run方法')
    
    
    class Dog(Animal):  # 一个Dog子类, 继承自Animal. 子类能获得父类的全部功能, 称之为继承
        pass
    
    class Cat(Animal):  # 一个cat子类
        pass
    
    
    xialing = Dog()  # 将子类实例化, 得到对象xialing, 验证该对象是否获得基类的方法
    xialing.run()
    
    
    # 这是Animal基类内部的run方法
    # Process finished with exit code 0
    • Dog类继承自Animal类
    • Dog类实例化生成的对象xialing获得了Animal类的方法

     

    多继承

    class Animal(object):
        def climb(self):
            print('动物会爬树')
    
    
    class Human(object):
        def talk(self):
            print("人类会说话")
    
    
    class Immortal(object):
        def magic(self):
            print("神仙会仙术")
    
    
    class Monkey(Animal, Human, Immortal):
        pass
    
    wukong = Monkey()
    wukong.climb()
    wukong.talk()
    wukong.magic()
    
    # 动物会爬树
    # 人类会说话
    # 神仙会仙术
    # Process finished with exit code 0
    • Monkey类 继承了三个基类
    • Monkey类实例化的对象获得了三个类的方法

     

    多继承里的查找顺序问题

     

    class Animal(object):
        def climb(self):
            print('动物会爬树')
    
    
    class Human(Animal):
        def climb(self):
            print("人类会会爬树")
    
    
    class Immortal(Animal):
        def climb(self):
            print("神仙也会爬树")
    
    
    class Monkey(Human, Immortal):
        pass
    
    wukong = Monkey()
    wukong.climb()
    print(Monkey.__mro__)
    
    # 人类会会爬树
    # (<class '__main__.Monkey'>, 
    # <class '__main__.Human'>, 
    # <class '__main__.Immortal'>,
    # <class '__main__.Animal'>, 
    # <class 'object'>)
    
    # Process finished with exit code 0
    • 自下往上, 由近及远的去查找climb方法. (注意monkey类的继承顺序, Human在前)
    • 如果Monkey类里增加climb 方法, 并print(“猴子当然会爬树”), 那么最终的输出结果是猴子当然会爬树.
    • 可通过 __mro__ 来查看python内部的查找顺序
    • 这个涉及到语言本身的算法, 如广度优先.

    方法重写

    适用条件

    • 参数列表必须完全和被重写方法的参数列表一致. 如果Human类的__init__方法中添加其他参数, 则会报错. TypeError: Animal.__init__() takes 3 positional arguments but 4 were given
    • 返回类型必须完全和被重写方法的返回类型一致.
    • 访问修饰符的限制一定要大于被重写方法的访问修饰符.

     

    class Animal(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def describe(self):
            print('动物会爬树')
    
    
    class Human(Animal):
        def __init__(self, name, age):
    
            # Animal.__init__(self, name, age)    # 手动调用
            super().__init__(name, age)     # super()是一个特殊的类,调用super得到一个对象,该对象指向父类的名称空间。
            self.title = "齐天大圣"          # 拓展其他的属性
    
        def describe(self):
            print(self.name)
            print(self.age)
            print(self.title)
    
    wukong = Human('悟空', '99999')
    wukong.describe()
    
    
    # 悟空
    # 99999
    # 齐天大圣
    # Process finished with exit code 0

     

    方法重载

    重载是让类以统一的方式处理不同类型数据的一种手段。

    适用条件:

    • 一个类里面
    • 方法名字相同
    • 参数不同

    基本设计本原则:

    • 仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载

    解决的问题:

    • 可变参数类型
    • 可变参数个数

    由此可知:

    • 函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数.
    • 函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。

    进而得到:

    • Python 里并没有语言规范上的方法重载.
    • 但有其他语言里方法重载里的功能
    • 因为Python函数自身的独特性和灵活性,  已经可以实现其他语言里的方法重载功能.

     

    多态

     

    定义: 同一种行为 , 对于不同的子类对象有不同的行为表现

    适用条件:

    • 继承:多态必须发生在父类和子类之间
    • 重写: 子类重写父类的方法
    class Animal(object):  # 创建一个基类
        def describe(self):
            print('这是Animal基类')
    
    class Dog(Animal):
        def describe(self):
            print('这是继承Animal类的Dog类, 这里是狗')
    
    
    class Cat(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Cat类, 这里是猫')
    
    xialing = Dog()
    xialing.describe()
    
    lihua = Cat()
    lihua.describe()
    
    # 这是继承Animal类的Dog类, 这里是狗
    # 这是继承Animal类的Cat类, 这里是猫
    # Process finished with exit code 0
    

     

    • 黠灵 之于Animal类, 得到了Dog子类的方法.
    • 狸花 之于Animal类, 得到了Cat子类的方法.
    • 同一个机器, 能根据需求制造出不同的产品
      • Animal类  = 机床
        • 重写和继承 = 调整和设计程序
          • 第一次加工出齿轮/黠灵
          • 第二次加工出叶片/狸花猫

    多态的优越性

     

    class Animal(object):  # 创建一个基类
        def describe(self):
            print('这是Animal基类')
    
    class Dog(Animal):
        def describe(self):
            print('这是继承Animal类的Dog类, 这里是狗')
    
    
    class Cat(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Cat类, 这里是猫')
    
    
    class Bird(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Bird类, 这里是鸟')
    
    def uniform_invoke(obj):
        obj.describe()
    
    obj_list = [Dog(), Cat(), Bird()]
    for i in obj_list:
        uniform_invoke(i)
    
    # 这是继承Animal类的Dog类, 这里是狗
    # 这是继承Animal类的Cat类, 这里是猫
    # 这是继承Animal类的Bird类, 这里是鸟
    # 
    # Process finished with exit code 0

    这里需要一点想象力, 假设之后需要拓展功能代码, 直接增加bird类, 然后让如列表即可.

     

    鸭子类型 duck typing

    • 鸭子类型(英语:duck typing)是动态类型的一种风格。
    • 在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由”当前方法和属性的集合”决定。
    • “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
    • “当看到一个人走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这个人就可以被称为鸭子。”
    • 在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。

     

    class Animal(object):  # 创建一个基类
        def describe(self):
            print('这是Animal基类')
    
    class Dog(Animal):
        def describe(self):
            print('这是继承Animal类的Dog类, 这里是狗')
    
    
    class Cat(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Cat类, 这里是猫')
    
    
    class Bird(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Bird类, 这里是鸟')
    
    #### 鸭子类型特性
    class Human(object):
        def describe(self):
            print('这是基类Human')
    
    class Student(Human):
        def describe(self):
            print('这是继承Human类的学生类, 这里是学生')
    ####
    
    
    def uniform_invoke(obj):
        obj.describe()
    
    obj_list = [Dog(), Cat(), Bird(), Student()]
    for i in obj_list:
        uniform_invoke(i)
    
    # 这是继承Animal类的Dog类, 这里是狗
    # 这是继承Animal类的Cat类, 这里是猫
    # 这是继承Animal类的Bird类, 这里是鸟
    # 这是继承Human类的学生类, 这里是学生
    #
    # Process finished with exit code 0

     

    只关注Student()对象本身, 而不管其属于Animal类还是Human类.

     

    封装

     

    前置基础知识

    前瞻与概括

    • 实例方法,第一个参数必须要默认传实例对象,一般习惯用self。如果类调用实例方法,需要显示的传self, 也就是实例对象自己
      • def function_name(self, parm1, parm2)
    • 类方法(由@classmethod装饰的方法),可以被类或类的实例对象调用。第一个参数必须要默认传类,一般习惯用cls。
      • @classmethod
            def function_name(cls):
    • 静态方法,参数没有要求 (但可以带参数)。静态方法(由@staticmethod装饰的方法)
      • @staticmethod
            def function_name():
      • 静态方法可以通过类对象和实例对象去访问, 但是很少使用实例对象去访问.
        • 静态方法可通过类对象直接方法, 不需要额外的实例化操作,  减少代码和开销.
        • 由于静态方法主要来存放逻辑性的代码,本身和类以及实例对象没有交互. 即在静态方法中,通常不会涉及到类中方法和属性的操作.
    • 备注:
      • 如果存在相同名称的 实例属性 和 类属性, 实例属性的优先级更高.

    类属性和实例属性

    类属性, 类对象和实例对象, 都可以访问

    实例属性, 只有实例对象可以访问

     

    class Animal(object):
        name = "黠灵"
    
        def __init__(self, age):
            self.age = age
    
    xialing = Animal(3)
    
    print(xialing.name)     # 实例对象 访问 类属性 √
    print(xialing.age)      # 实例对象 访问 实例属性 √
    
    print(Animal.name)      # 类对象 访问 类属性 √
    # print(Animal.age)     # 类对象 访问 实例属性 × # AttributeError: type object 'Animal' has no attribute 'age'
    

     

     

    类方法

    class Animal(object):
        name = "黠灵"  # 声明一个类属性
    
        @classmethod  # 运用 @classmethod 声明一个类方法
        def describe(cls):
            return cls.name
    
    xialing = Animal()
    
    print(xialing.describe())  # 实例对象 访问 类方法 √
    print(Animal.describe())   # 类对象 访问 类方法 √
    
    # 黠灵
    # 黠灵
    # Process finished with exit code 0
    通过类方法修改类属性
    class Animal(object):
        name = "黠灵"  # 声明一个类属性
    
        @classmethod  # 运用 @classmethod 声明一个类方法
        def describe(cls):
            return cls.name
    
        @classmethod  # 运用 @classmethod 声明一个类方法
        def describe_modified(cls, parm):
            cls.name = parm
    
    
    xialing = Animal()
    print(xialing.describe())  # 实例对象 访问 类方法 √ 黠灵
    print(Animal.describe())   # 类对象 访问 类方法 √ 黠灵
    
    Animal.describe_modified('狸花')
    print(Animal.describe())  # 狸花
    
    # 类方法可以修改类属性

    静态方法

    class Animal(object):
        name = "黠灵"  # 声明一个类属性
    
        @classmethod  # 运用 @classmethod 声明一个类方法
        def describe(cls):
            return cls.name
    
    
        @staticmethod
        def describe_again():
            return Animal.name
    
    xialing = Animal()
    print(xialing.describe())  # 实例对象 访问 静态方法 √ 黠灵 
    print(Animal.describe())   # 类对象 访问 静态方法 √ 黠灵
    
    print(xialing.describe_again())  # 实例对象 访问 静态方法 √ 黠灵 (一般不会这么做)
    print(Animal.describe_again())   # 直接通过类对象 访问静态方法 √ 不需要实例化.
    
    直接通过类对象 访问静态方法
    import  time
    class TimeTest:
        # def __init__(self,hour,min,second):
        #     self.hour=hour
        #     self.min = min
        #     self.second = second
    
        @staticmethod
        def showTime():
            return time.strftime("%H:%M:%S",time.localtime())
            pass
        pass
    
    print(TimeTest.showTime())
    
    
    # t=TimeTest(2,10,15)
    # print(t.showTime()) #没有必要通过这种方式去访问 静态方法
    带参数的静态方法
    class Demo(object):
        
        @staticmethod
        def summation(a, b):
            print(a + b)
            
    Demo.summation(1,2)
    
    # 3
    Process finished with exit code 0

     

    什么是封装?

    如何实现封装

    私有属性

    私有化的实例/类属性, 不能在类外部直接访问

    没有使用私有属性封装的代码形式

    class Animal(object):
        def __init__(self):
            self.name = "黠灵"
            self.age = "6"
    
    xialing = Animal()
    print(xialing.name, xialing.age)

    使用了私有属性封装的代码形式

    class Animal(object):
        def __init__(self):
            self.__name = "黠灵"      # 使用双下划线, 设置一个私有化属性__name
            self.age = "9999"
    
        # __str__ 将类的实例变成 str; print打印出来是个对象;使用了就把对象变成字符串
        def __str__(self):
            return f'狗名:{self.__name} | 狗命 {self.age}'
    
    xialing = Animal()
    print(xialing)
    # print(xialing.name, xialing.age) # AttributeError: 'Animal' object has no attribute 'name'
    

     

    如果不理解__str__,  可以看另一个实例

    
    class Animal(object):
        def __init__(self):
            self.__name = "黠灵"      # 设置一个私有化属性
            self.age = "9999"
    
        def get_data(self):
            print(f'狗名:{self.__name} | 狗命 {self.age}')
    
    xialing = Animal()
    xialing.get_data()      # 此时在类的外部, 实例对象, 通过类内部的get_data, 间接地访问__name
    print(xialing.age)      # 此时在类的外部, 实例对象, 直接访问类内部的age
    #print(xialing.__name)  # 此时在类的外部, 实例变量, 直接访问类内部的私有变量 __name (报错)
    
    

     

    父类中私有化的实例/类属性, 不能被子类继承
    class Animal(object):
        def __init__(self):
            self.__name = "黠灵"      # 设置一个私有化属性
            self.age = "9999"
    
        # __str__ 将类的实例变成 str; print打印出来是个对象;使用了就把对象变成字符串
        def __str__(self):
            return f'狗名:{self.__name} | 狗命 {self.age}'
    
    xialing = Animal()
    print(xialing)
    # print(xialing.name, xialing.age) # AttributeError: 'Animal' object has no attribute 'name'
    
    class Cat(Animal):
        pass
    
    lihua = Cat()
    print(lihua.__name)  # AttributeError: 'Cat' object has no attribute '__name'
    # 子类的Cat的实例化对象lihu 在类的外部, 无法访问__name 属性

     

    私有化的实例/类属性, 可以在类的内部访问

    以上代码已证实

     

    私有方法

     

    class Animal(object):
        def fly(self):
            print("fly")
    
        def climb(self):
            print('climb')
    
    
    xialing = Animal()
    xialing.fly(), xialing.climb() # 正常访问
    

     

    class Animal(object):
        def __fly(self):    # 一个私有化方法
            print("fly")
    
        def climb(self):
            print('climb')
    
    
    xialing = Animal()
    xialing.fly(), xialing.climb()  # 报错
    

     

    property方法

     

    封装的目的不是为了完全的封闭, 而是为了更安全, 更高效的提供数据. 大多数情况下, 私有化的属性和方法, 还是要和外部进行交互. 正常情况, 可以在类的内部设计一个用于和外部交换的方法. 但除此之外, Python 还提供了一个专门的函数 property(). 直观和通俗的讲:

    • 通常访问私有属性, 需要设置两个方法, 一个修改, 一个访问.
    • 这种调用方式, “感觉”是调用一个方法, 而不是访问属性. 那么如何让调用者可以直接访问属性, 而且我们又能控制的方式 提供给调用者?
    • property
    • property 还可以和装饰器结合使用, 快速高效地实现一些功能

     

    class Animal(object):
        def __init__(self):
            self.__age = 6
    
    
        def get_data(self):
            return self.__age
    
        def set_data(self, value):
           self.__age = value
    
    
        age = property(get_data, set_data)
    
        print(type(age))
    
    xialing = Animal()
    xialing.age = 999
    print(xialing.age)  

    语法特性

    • property 可以修饰类中属性的 get 、 set 和 del 方法,以及为属性对象创建文档字符串。
    • 原型为 class property(fget=None, fset=None, fdel=None, doc=None) ,它返回 property 属性。
    • fget 是获取属性值的函数。 fset 是用于设置属性值的函数。 fdel 是用于删除属性值的函数。 doc 为属性对象创建文档字符串。

    本实例中

    • xialing.age = 999 调用 setter
    • xialing.age 调用 getter

     

    单例模式

    魔术方法 __new__

    单例模式是常用和典型的”封装”形式和提现. Python里的单例模式有很多种实现方式,  比如模块, 装饰器, 类方法.  这里是通过魔术方法new.

    • 当我们实例化一个对象时,是先执行了类的__new__方法,实例化对象.
    • 然后再执行类的__init__方法,对这个对象进行初始化.
    • 那么在new 和 init之间, 可以进行判断, 如果类里面具有某个属性, 则不再创建对象.
    • 从而实现”一个类里面, 只有一个实例”.

     

    class Animal(object):
        def __init__(self):
            pass
    
    
        def __new__(cls, *args, **kwargs):
            cls._instance = super().__new__(cls, *args, **kwargs)
            return cls._instance
    
    xialing = Animal()
    print(id(xialing))
    
    huban = Animal()
    print(id(huban))
    
    
    # 1639090149520
    # 1639090149648
    # Process finished with exit code 0

    可以看到生成两个类

     

    class Animal(object):
        def __init__(self):
            pass
    
    
        def __new__(cls, *args, **kwargs):
            if not hasattr(cls,'_instance' ):
                cls._instance = super().__new__(cls, *args, **kwargs)
            return cls._instance
    
    xialing = Animal()
    print(id(xialing))
    
    huban = Animal()
    print(id(huban))
    
    
    # 2642697705424
    # 2642697705424
    # Process finished with exit code 0

    单例模式的实现

     

    动态绑定

    动态绑定实例方法

    import types
    
    class Animal(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        # def __str__(self):
        #     print(f'self.name | self.age')
    
    
    def add_method(self):
        print(f'{self.name} | {self.age}| {self.color}')
    
    
    xialing = Animal('黠灵', '10')
    
    # 动态增加属性 color
    xialing.color = 'yellow'
    
    # 动态绑定add_method方法
    xialing.describe = types.MethodType(add_method, xialing)
    
    # 调用动态绑定的方法
    xialing.describe()

    实例方法describe

     

    动态绑定类方法

    import types
    
    class Animal(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        # def __str__(self):
        #     print(f'self.name | self.age')
    
    
    def add_method(self):
        print(f'{self.name} | {self.age}| {self.color}')
    
    @classmethod
    def add_classmethod(cls):
        print('Animal类 动态添加了 类方法describe_classmethod ')
    
    Animal.describe_classmethod = add_classmethod
    Animal.describe_classmethod()
    
    # Animal类 动态添加了 类方法describe_classmethod 

     

    动态绑定静态方法

    import types
    
    class Animal(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        # def __str__(self):
        #     print(f'self.name | self.age')
    
    
    def add_method(self):
        print(f'{self.name} | {self.age}| {self.color}')
    
    @staticmethod
    def add_staticmethod():
        print('Animal类 动态添加了 静态方法describe_staticmethod ')
    
    Animal.describe_staticmethod = add_staticmethod
    Animal.describe_staticmethod()
    
    # Animal类 动态添加了 静态方法describe_staticmethod 

    限制属性动态添加 __slot__()

     

    class Animal(object):
        __slots__ = ('name', 'age')
        # __slots__ = ('name') # AttributeError: 'Animal' object has no attribute 'age'
    
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    
        def describe(self):
            print(f'{self.name} | {self.age}')
    
    xialing = Animal('黠灵', '10')
    xialing.describe()