香中别有韵,清极不知寒-----------面向对象

一、类与对象介绍

1.面向对象
    面向对象就是一种编程思想
    面向过程也是一种编程思想  
    # 核心就是过程
    # 过程就是'流水线',用来分步骤解决问题的
    # 过程的核心思想就是将程序流程化进而简单化
    面向对象  # 核心是对象二字
    对象:对象就是容器,容器是用来盛放东西的(盛放什么东西)
        用来盛放数据与功能的。
        ---所以说对象就是'数据'跟'功能'的集合体
        ---对象就是将程序'整合'
        程序=数据+功能
        学习对象就是将数据与功能整合到一起的方法
        函数是放功能的容器
        面向对象是一种更好的实现数据和功能整合到一起的方法
    类:就是将对象做一个个区分,为了解决对象重复存取的问题
        所以说类也是一种容器,该容器用来存放同类对象共有的数据与功能。
        也可以将类叫做对象。
        我们在访问数据的时候是先从对象里面的空间里面找,然后在从类的空间里面找

----------------类的语法:----------------
1.先定义类
    class 定义类, 类中可以有任何的代码
    1.变量的定义,2.功能的定义
    类体代码在定义时候就运行了,就会产生名称空间。
    这个时候对比一下(函数会在调用的时候才产生名称空间,而类在定义阶段就产生名称空间)
    print(Student.__dict__)  # 可以查看类的名称空间里面的名字
    类中存放的是对象共有的东西,本质是给对象用的,类自己也可以用
    print(Student.__dict__['sss'])  # 可以取出类里面的值
    python给我们提供了一种专门的取值方法
    属性访问语法,这个sss不能称为是字符串,而是Student这个类的一个属性
    类的属性无非就两种(1.访问数据属性,2.访问函数属性)
    print(Student.sss)  # 本质就跟上面的取值一样,但是简便了很多
    类跟对象是有关联的
2.在调用类产生对象
    类跟对象是有关联的,如果想让对象跟类有关联,必须用刚刚造出来的类调用一     下。 需要强调的是这个调用不会执行代码,因为类在定义的时候就执行代码过     了。只要调用Student()就会帮你产生一个对象,就会建立类和这个对象的关联     此时这个对象还是空的对象。需要你往里面添加。然后可以得到他的返回值,这个返回值就是对象。对象放的也是数据跟功能
    所以对象在刚刚造出来的时候,应该为他初始化定制他独有的属性(数据初始化)
    最好是不用自己产生,在调用的时候会自动把对象初始化
    所以把下面的函数也放进类里面。只要调用类,就会把空对象传给__init__这个函数,调用的时候后三个参数需要自己来传
    def __init__(obj,参数1,参数2,参数3.....):  
        obj.stu_name = 参数1
        obj.stu_age = 参数2
        obj.stu_gender = 参数3
    在类定义阶段不会触发,在调用阶段自动触发
    Stu1_obj = Student(参数1,参数2,参数3)
    调用类的过程又称为实例化(你属于人类,但是不能说人类是你,你只是人类实际存在的一个案例,这就叫实例)
    调用类是为了产生对象的,这个过程有人称之为实例化,将类这个抽象的东西,实例化
    -----------------调用类发生了三件事------------
    1.先产生一个空对象
    2.会调用类中的__init__方法,然后将空对象以及调用类时括号内传入的参数一同传给__init__方法
    3.返回初始化完的对象,然后可以做一个赋值操作可以看到对象里面的结果
    ------------总结__init__方法------------
    1.会在调用类时自动触发执行,用来为对象初始化自己独有的数据
    2.__init__内应该存放的是为对象初始化属性的功能,当然你也可以存放任意你想写的代码,想要在类调用时就立即执行的代码都可以放到该方法内
    3.调用类的返回值并不是__init__,  __init__方法必须返回None,不能返回非None的值
    -------------------一个完整的类的形式----------
    class Student:
        # 1.变量的定义
        stu_school='oldboy'
        # 空对象
        def __init__(obj,x,y,z):
            obj.stu_name=x
            obj.stu_age=y
            obj.stu_gender=z
         
        # 功能的定义
        def tell_stu_info(stu_obj):
            print('学生信息:名字%s 年龄%s 性别%s' % (
            stu_obj['stu_name'],
            stu_obj['stu_age'],
            stu_obj['stu_gender']))
        def set_info(stu_obj,x,y,z):
            stu_obj['stu_name']=x
            stu_obj['stu_age']=y
            stu_obj['stu_gender']=z
stu1_obj = Student('egon',18,'male')
3.属性查找的顺序
类中存放是与对象共有的数据与功能
1.类可以访问:访问的是类的数据属性或者是类的函数属性
2.其实类中的东西是给对象用的:类的数据属性是共享给所有对象用的,大家访问的地址都一样
类的函数属性是绑定给对象用的,不同的对象
绑定方法的特殊之处,谁来调用谁当做第一个参数传入该函数内
                                          

隐藏属性

一:封装介绍
    封装是面向对象三大特性最核心的一个特性
    封装<->整合


二、将封装的属性进行隐藏操作
1、如何隐藏:在属性名前加__前缀,就会实现一个对外隐藏属性效果
	该隐藏需要注意的问题:
        I:在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如			Foo._A__N,所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。
            class Foo:
                __x = 1  # _Foo__x

                def __f1(self):  # _Foo__f1
                    print('from test')


            print(Foo.__dict__)
            print(Foo._Foo__x)
            print(Foo._Foo__f1)

		II:这种隐藏对外不对内,因为__开头的属性会在检查类体代码语法时统一发生变形
            class Foo:
                __x = 1  # _Foo__x = 1

                def __f1(self):  # _Foo__f1
                    print('from test')

                def f2(self):		
                    print(self.__x) # print(self._Foo__x)
                    print(self.__f1) # print(self._Foo__f1)

            print(Foo.__x)
            print(Foo.__f1)
            obj=Foo()
            obj.f2()

		III: 这种变形操作只在检查类体语法的时候发生一次,
            # 之后定义的__开头的属性都不会变形
            class Foo:
                __x = 1  # _Foo__x = 1

                def __f1(self):  # _Foo__f1
                    print('from test')

                def f2(self):
                    print(self.__x) # print(self._Foo__x)
                    print(self.__f1) # print(self._Foo__f1)

            Foo.__y=3
            print(Foo.__dict__)
            print(Foo.__y)

            class Foo:
                __x = 1  # _Foo__x = 1

                def __init__(self,name,age):
                    self.__name=name
                    self.__age=age

            obj=Foo('egon',18)
            print(obj.__dict__)
            print(obj.name,obj.age)
		
2、为何要隐藏?
    I、隐藏数据属性将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之	上可以附加额外的逻辑来对数据的操作进行严格地控制:
    class People:
        def __init__(self, name):
            self.__name = name

        def get_name(self):
            # 通过该接口就可以间接地访问到名字属性
            # print('小垃圾,不让看')
            print(self.__name)

        def set_name(self,val):
            if type(val) is not str:
                print('小垃圾,必须传字符串类型')
                return
            self.__name=val


    obj = People('egon')
    # print(obj.name) # 无法直接用名字属性
    # obj.set_name('EGON')
    obj.set_name(123123123)
    obj.get_name()
II、隐藏函数/方法属性:目的的是为了隔离复杂度

property

# property 其实就是装饰器
    class People:
        def __init__(self, name, height, weight):
            self.name = name
            self.height = height
            self.weight = weight

        @property
        def blm(self):
            return self.weight / (self.height ** 2)


    obj1 = People('ame', 1.75, 85)
    print(obj1.blm)




# 在隐藏属性的时候 property的用法
    class People:
        def __init__(self, name):
            self.__name = name

        @property
        def name(self):
            print(self.__name)

        @name.setter
        def name(self, val):
            if type(val) is not str:
                print('请输入字符串')
            self.__name = val

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

    obj1 = People('Ame')
    # obj1.get_name()
    # obj1.set_name('Yuki')
    # obj1.get_name()
    obj1.name
    obj1.name = 'Yuki'
    obj1.name
    obj1.name
    del obj1.name

继承

1.什么是继承
	'''
	继承其实就是新创建的类,新创建的子类可以又叫派生类,父类又可以叫基类或者超类
    需要注意的是python支持多继承  即一个字累可以有一个父类或者多个父类,子类可以继承父类的数据
    ps:  python2的继承分为经典式和新式类之分
            新式类:继承了object的子类,以及子类的子类子子类
            经典类:没有继承了object的子类,以及子类的子类子子类
    	但是python3里没有这么分,所有的都会默认继承object,所以都是新式类
    多继承的优点缺点
    	优点  子类继承了多父类所有东西,代码能更简洁,更加被利用化
    	缺点  1 违背了人的正常逻辑,不可能拥有多个“父亲”
    		 2 代码可读性会变得很差
    		 3 不建议用到多继承,有可能会遇到可恶的菱形问题,扩展性也会变差
    		 如果真得一个子类要多继承的话,得使用Mixins
    '''
2.为什么用继承
	# 因为要解决类与类之间的代码冗余问题
    
3.怎么用继承
    class School:
        school = 'Oldboy'

        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender


    class Student(School):
        def choose(self):
            print(f'{self.name}正在选课')


    class Teacher(School):
        def __init__(self, name, age, gender, level, salary):
            School.__init__(self, name, age, gender)
            self.level = level
            self.salary = salary

        def check(self):
            print(f'{self.name}正在批改作业')


    stu_obj = Student('Ame', 18, 'male')
    stu_obj.choose()
    tea_obj = Teacher('Yuki', 18, 'female', 10, 10000)
    tea_obj.check()

单继承背景下的属性查找

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
# 我们会发现后来没有找到Foo.f1,找到了Bar.f1,因为self是obj给的,所以后来就去Bar里找f1,那么我们怎么找到Foo.f1呢

# 示范一
class Foo:
    def f1(self):
        print('Foo.f1')

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


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


obj = Bar()
obj.f2()
    
# 示范二
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()

菱形问题(钻石问题)

1.什么是菱形问题
class A(object):
    def test(self):
        print('A')


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


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


class D(B, C):
    pass


obj = D()
obj.test()  # 像这样的就是菱形问题,D------B, C------A  注意 C--------B, A------object  这样不是菱形问题
# 像这样遇到B, C同级应该会选择B还是C呢,查找方法得依靠C3算法得出来的MRO,注意 python2没有MRO

    class A(object):
        def test(self):
            print('A')


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


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


    class D(B, C):
        pass


    obj = D()
    obj.test()
    print(D.mro()) 
'''
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
就会依靠这个去寻找
注意 1.子类会先于父类被检查
	2.多个父类会根据他们在列表中的顺序检查
	3.下一类存在多个合法的选择,就会选择第一个父类
'''

非菱形问题的属性查找顺序

# 经典式与新式类 之间查找顺序都是一样的

菱形问题的属性查找顺序

# 经典类是深度查找,即一路走到黑,找不到再往右边去搜寻
# 新式类是广度查找,即每一路都走,但是不找最后,往右边全部搜寻完了,再去找最后一个

Mixins机制

    class Vehicle:  # 交通工具
        pass


    class Helicopter(Vehicle):  # 直升飞机
        def fly(self):
            pass


    class CivilAircraft(Vehicle):  # 民航飞机
        def fly(self):
            pass


    class Car(Vehicle):  # 小轿车
        pass

'''
从这可以看出 飞机们都有飞行的功能,我们应该把飞行功能代码都写入父类,防止代码冗余,但是这样的话
小汽车:???,我也会飞了
所以我们并不能把飞行功能加入交通工具这个父类
'''
# 处理办法
    class Vehicle:  # 交通工具
        pass


    class Aircraft:  # 飞行器
        def fly(self):
            pass


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


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


    class Car(Vehicle):  # 小轿车
        pass
'''
我们加入了个新父类,来解决汽车不能飞,但是飞机能飞的问题,
但是直升飞机和民航飞机就发出了疑问:??怎么我们有了两个亲爹
所以这样是不合人的逻辑了,所以mixin跳了出来,摇了摇头,帮助他们解决了问题
'''
    class Vehicle:  # 交通工具
        pass


    class AircraftMixin:  # 飞行器
        def fly(self):
            pass


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


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


    class Car(Vehicle):  # 小轿车
        pass
'''
mixin其实不是特殊的直白有效的代码,只是一个标志,一个功能的标志,往一个类加入这个Mixin就代表他其实不是真正的父亲,只是一个功能的代表,真正的亲爹是交通工具
同时也要注意mixin用法也有限制!!!!
	1. mixin代表的是一个功能,不要代表一个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
	2. 然后他的责任很单一,如果有多种类功能,则要写多个mixin,不能挤一块,一个类是可以多继承很多mixin的
	3.他不依赖于子类的实现
	4.子类就算没有继承mixin,他还是能进行,只不过缺少了功能,比如,飞机没有继承飞,但是他还是可以载客
Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差,并且更恶心的是,在继承的层级变多时,代码阅读者在定位某一个方法到底在何处调用时会晕头转向
'''

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

# 在子类中直接调用父类的功能
	 class School:
        school = 'Oldboy'

        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender

            
    class Teacher(School):
        def __init__(self, name, age, gender, level, salary):
            School.__init__(self, name, age, gender)
            self.level = level
            self.salary = salary
            
# super()调用父类提供给自己的方法----严格依赖继承关系
# 调用super()会得到一个特殊的对象,会自动追寻当前类MRO,去当前父类中找属性
    class School:
        school = 'Oldboy'

        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender


    class Teacher(School):
        def __init__(self, name, age, gender, level, salary):
            super().__init__(name, age, gender)  # 传入的是方法,自动调用对象,所以不用加self
            self.level = level
            self.salary = salary


    tea_obj = Teacher('Ame', 18, 'male', 10, 10000)
    print(Teacher.mro())
    print(tea_obj.__dict__)

多态与鸭子类型

1.什么是多态
其实就是一个事物有多种形态
2.在程序中表达多态
    class Animal:
        pass


    class People(Animal):
        pass


    class Dog(Animal):
        pass


    class Pig(Animal):
        pass  
# 动物就有多种形态,称之为多态,那么多态的好处是什么呢,就比如动物都会叫
    class Animal:
        def say(self):
            print('动物都会叫....')


    class People(Animal):
        def say(self):
            super().say()
            print('嘤嘤嘤')


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


    class Pig(Animal):
        def say(self):
            super().say()
            print('哼哼哼')


    obj1 = People()
    obj2 = Dog()
    obj3 = Pig()
    obj1.say()
    obj2.say()
    obj3.say()
    # 好处就是我们在调用子类的时候并不用想子类是什么,只用想着父类就是动物,肯定会叫,这是所有的语言多态的优点,但是python有更好玩的,那就是鸭子类型
    3.什么是鸭子类型
    	其实就是比如 一个人在街上模仿鸭子叫声,走路,甚至还去河里游泳,我们就可以去把这个当成鸭子,代码也一样的道理,如果都没有指定父类,但是他们都有相似之处,就直接当成是一样来看
        class Cpu:
    def read(self):
        print('cpu read')

    def write(self):
        print('cpu write')


    class Memory:
        def read(self):
            print('Memory read')

        def write(self):
            print('Memory write')


    class Txt:
        def read(self):
            print('txt read')

        def write(self):
            print('txt write')
    obj1 = Cpu()
    obj2 = Memory()
    obj3 = Txt()
    obj2.write()
    obj1.write()
    obj3.write()
    # 正如cpu Memory都是硬件,TXT则是软件,真要分父类是分开的,但是他们都有读写功能,所以就可以看作一块,这就是鸭子类型

classmethod与绑定方法

1.绑定方法
	特殊之处在于将调用者本身传入给第一个参数
    # 绑定给对象的方法  调用者是对象 传入的也是对象
    class Mysql:
        def __init__(self, ip, port):
            self.ip = ip
            self.port = port
    
    
    obj1 = Mysql('1,0,1,0', 3306)
    # 绑定给类的方法  调用者是类,传入的也是类
	import setting


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

        def func(self):
            print(f'{self.ip}{self.port}')

        @classmethod
        def from_conf(cls):
            return cls(setting.IP, setting.PORT)


        obj1 = Mysql.from_conf()
        print(obj1.__dict__)
        
        # 在setting文件里放了IP 和 PORT,直接这里调用


staticmethod与非绑定方法

# 非绑定方法其实就是静态方法,当一个函数不需要对象也不需要类的时候,但是想放在类下面,就用@staticmethod装饰器
# 没有绑定给任何人,调用者可以是类 可以是对象 不会有自动传入参数的效果
    class Mysql:
            def __init__(self, ip, port):
                self.ip = ip
                self.port = port
                
            @staticmethod    
            def creat_id():
                import uuid
                return uuid.uui4()
            

反射

# 什么是反射
	反射就是一个对象(不见棺材不落泪)到最后才知道他是什么属性
 
# 为什么要用反射
	因为假如在一个类中,传入了对象,然后想找到对象的属性,用反射就很容易找到
    
# 怎么用反射
	class School(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age

        def say(self):
            print(f'<{self.name},{self.age}>')


	obj = School('Ame', 10)
    # 假如在以后的工作中,你的同事写好了代码跟你交接,你只知道有个对象obj,该怎么知道obj的属性是什么呢
    # 要用到dir()方法
    print(dir(obj))  # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', .......]
    # dir()会显示出obj所有的属性,所以在运用反射的4个内置函数便OK
    print(hasattr(obj, 'name'))  # hasattr 是判断有没有这个属性   True
    print(getattr(obj, 'name'))  # getattr 是调出这个属性的值     Ame
    setattr(obj, 'name', 'Yuki')  
    print(obj.name)  # setattr 是修改这个属性        Yuki
    delattr(obj, 'name')  # delattr 删除这个属性
   
# 用途案例
	class People(object):
        def put(self):
            print("lalala")

        def get(self):
            print("lblblb")

        def interactive(self):
            methon = input('------>')

            if hasattr(self, methon):
                print(self, methon)
            else:
                print('指令不存在')
	
    obj = People()
    obj.interactive()
# 从这可以看出,发射就是用来判断未知的对象属性的

内置方法

# 什么是内置方法
	以__开头__结尾的就是内置方法
	特点 会自动触发调用
    
# 为什么用内置方法
	为了定制化我们的类或者对象
    
# __str__ :打印对象时,自动触发,然后会将返回值显示出来
	class School:
        def __init__(self, name, age):
            self.name = name
            self.age = age

        def __str__(self):
            return f'{self.name},{self.age}'


    obj = School('Ame', 18)
    print(obj)  # Ame,18            由此可以看出来 直接打印对象,然后就会输出__srt__

# __del__:删除的时候会自动调用
	class School:
        def __init__(self, name, age):
            self.name = name
            self.age = age

        def __del__(self):
            print('lalala')


    obj = School('Ame', 18)
    print("-------------") 
    '''
    -------------
    lalala
    '''
    # 从输出结果可以看 lalala自动打印了出来 这是为什么,因为程序结束后,pycharm要回收空间,所以会自动删除,然后__del__就打印了出来,如果在print之前删除呢
    
    class School:
        def __init__(self, name, age):
            self.name = name
            self.age = age

        def __del__(self):
            print('lalala')


    obj = School('Ame', 18)
    del obj
    print("-------------")
    '''
    lalala
    -------------
    '''
    # 所以__del__是删除的时候会自动调用,那么这个用途在哪的,我们在一个程序结束后,pycharm会自动回收,但是只是回收自己的空间,操作系统的并没有回收,所以__del__是用来告诉pycharm回收系统空间的
    

元类

# 引入
	一切皆是对象
   
# 什么是元类
	用来实例化产生的类的类
    关系: 元类-------实例化-------类---------实例化--------对象

# 查看内置的元类:我们用class关键字定义的所有的类以及内置的类都由内置的元类type实例化产生的

class的机制

# class其实是由以下的步骤产生
    class_name = 'People'
    class_bases = (object,)
    class_dir = {}
    class_body = '''
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __del__(self):
        print('lalala')
    '''
    exec(class_body, {}, class_dir)
    print(type(class_name, class_bases, class_dir))  # <class '__main__.People'>
# 这种方法是自定义元类的精髓
    
# 其实平时 class People  的People 并不是类, 只是个变量名而已, 其中的功能代码集体才是一个类

自定义元类

class Mymeta(type):
    def __init__(self,x ,y ,z):  # 这边必须要传四个参数, 因为class people的存在会把class过程的参数传给Mymeta
        print('123')


class People(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.bases = age

    def say(self):
        print(f'<{self.name},{self.age}>')
posted @ 2021-10-28 09:00  Ame林汀  阅读(103)  评论(0)    收藏  举报