面向对象

目的:
    写出有意义的面向对象的代码,其作用就是封装代码
定义时注意:
    命名规范 Student、StudentPages
    类体不能什么都不写,要写pass

定义示例:
    class Student():
        # 开始类体的编写
        name = ''   
        age = 0

        def print_file():
            print('age = '+str(age))
    stu = Student() #不需要使用new 来实例化这个类
调用类的方法:
    stu.print_file()
    注意上述调用会报错  
        # TypeError: print_file() takes 0 positional arguments but 1 was given
    做如下修改:
        def print_file(self):
            print('age = '+str(age))
    仍然报错,报错age没有定义
    继续修改,改完正确运行
        def print_file(self):
            print('age = '+str(self.age))

    正确示例:
        class Student():
            name = ''   
            age = 0
            def print_file(self):
                print('age = '+str(self.age))
        stu = Student() 
        stu.print_file()
        # 或者直接用:Student().print_file()
    注意:
        上述类体中,对于print_file函数,不能在类体里调用
        写类的模块,最好是只写类,然后通过其他模块来实例化调用什么的
            from c1 import Student
            Student().print_file()
            Student().age
        注意:
            如果c1.py中同时包含对Student类的实例化和调用,那么上述import时也会执行c1.py中的示例化和调用
            所以,最好是模块和类分开,便于调用时的清晰

方法 和 函数:

区别:
    方法是语言设计层面的考量,应用起来没什么区别
    类中的函数应该叫‘方法’,模块中的函数就叫‘函数’
    类中的变量应该叫‘数据成员’,模块中变量叫‘变量’

类和对象 通过实例化联系在一起

什么是类:
    就是数据及一些操作的有意义的封装,可以体现出数据特征和行为特征
    行为要联系主体,体现在类的设计中要具有现实意义
什么是对象:
    表示具体的类对象,类本身可以实例化多种多样的对象

通过实例化来创造对象的多样性,依靠类的构造函数实现

    class Student():
        name = ''   
        age = 0
        def __init__(self): # 至少需要添加self参数
            print('init')
        def print_file(self):
            print('age = '+str(self.age))
    stu = Student() #构造函数在实例化时自动调用
    stu.__init__()  
    #构造函数也可以调用,跟普通函数类似,但是不推荐这样用
    但是:对于构造函数 只允许返回None,返回其他则报错

为构造函数添加参数:
    def __init__(self,param1,param2):
    此时实例化类时,必须传入两个值:stu = Student('a','b')
构造函数通常的用法:
    来修改类的数据特征,即重置类的成员变量
        class Student():
            name = ''   
            age = 0
            def __init__(self, name, age): # 至少需要添加self参数
                name = name
                age = age
    上面的代码不报错,但是不能修改name和age的值,并不是因为变量作用域的问题
注意:
    类的变量的作用域 与 模块变量的作用域 完全不同!
    要注意区别类的行为和模块的行为

类变量 实例变量:

代码示例:
    class Student():
        name = ''       name 是类变量:与类相关
        age = 0         age  是类变量:与类相关
        def __init__(self, name, age):  
            self.name = name    self.name 实例变量:与对象相关
            self.age = age      self.age  实例变量:与对象相关

    s1 = Student('a',1) 作为实例变量传入
    s2 = Student('b',8) 作为实例变量传入

注意上述self可以换成任意名称:
    def __init__(this, name, age):  
        this.name = name  
        this.age = age  
    换成this也是对的,但是推荐使用默认的 self

使用区别:
    对象名.成员变量   取决于实例化时的构造 
    类名.成员变量     只跟类有关,不可改变
    应用场景:
        比如定义一个狗类叫做ClassA:
        里面有成员变量 动物种类、狗品种、狗毛色
        有构造函数,参数为品种、毛色,但动物种类变量就等于“狗”,构造时不修改
        实例化时借助构造函数,得到N个不同的狗对象ObjN,可以对应现实世界中不同的狗个体
        此时,ObjN.品种,就是此狗对象的对象属性
        而ClassA.动物类型,表明此类的特征属性,表示共同特性或者不属于个体特性的变量就可以作为类的成员变量

(类的机制)

类变量和实例变量的特性

示例代码:
    class Student():
        name = '类变量name'   
        age = 0
        def __init__(self, name, age): # 至少需要添加self参数
            name = name
            age = age
    obj = Student('实例变量name','实例变量age')
    print(obj.name)         #打印类变量name
    print(Student.name)     #打印类变量name
    print(obj.__dict__)     #打印{}
    print(Student.__dict__) #打印{'name': '类变量name', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__init__': <function Student.__init__ at 0x00000000006BFA60>, '__module__': '__main__', '__dict__':<attribute '__dict__' of 'Student' objects>, 'age': 0}

寻找相关变量的机制:
    如果尝试去访问对象的一个成员变量
    首先会在对象的变量列表obj.__dict__里去查找有没有
    否则,到类的变量列表Student.__dict__去寻找
    否则继续去类的父类中去寻找 

示例代码:
    class Student():
        name = '类变量name'   
        age = 0
        def __init__(self, name, age): # 至少需要添加self参数
            self.name = name
            self.age = age
    obj = Student('实例变量name','实例变量age')
    print(obj.name)         #打印类变量name
    print(Student.name)     #打印实例变量name
    print(obj.__dict__)     #打印{'age': '实例变量age', 'name': '实例变量name'}
    解释:
        修改为self.name之后,则是实例的变量,在构造函数中必须赋值给实例的变量
        定义实例方法例如构造函数时,需要self出现,但是调用实例方法时不需要出现self
    注意:
        self和实例、对象绑定,与类无关
        实例、对象可以调用的方法叫:实例方法,参数第一个必须为 self保留
        self可以换成其他名字,比如this,但位置必须是第一参数
        解释:
            意思是实例方法第一个参数应该保留,具体叫self还是this或其他无所谓,但推荐用 self

Python的类

--- 变量  --- 类变量
          --- 实例变量
--- 方法  --- 实例方法    self + .操作符 -> 修改实例变量
          --- 类方法
          --- 静态方法
--- 构造函数(特殊的实例方法,只是默认调用)

实例方法要操作变量:

    --- 实例方法    self + .操作符 -> 修改实例变量
        例如:
            class Student():
                name = '类变量name'   
                age = 0
                def __init__(self, name1, age): # 至少需要添加self参数
                    self.name = name1
                    self.age = age
                    print(self.name)    #访问的实例变量
                    print(name)             
                    #这也是访问的实例变量,但是访问的是形参name,如果形参不是name,那就会报错
                    #print(__dict__) 
            obj = Student('实例变量name','实例变量age')
            打印:
                实例变量name
                实例变量name

        注意:
            查找变量列表__dict__只能在外部调用时访问,在实例方法内无法打印
            实例方法中,方法参数不要和类变量名相同
            类变量定义时,不要与类内置变量重名

    --- 实例方法    修改类变量
        例如:
            class Student():
                name = '类变量name'   
                age = 0
                def __init__(self, name1, age): # 至少需要添加self参数
                    print('形参name1:'+name1)
                    self.name = name1
                    print('修改实例变量:'+self.name)
                    print('访问类变量法一:'+Student.name) 
                    print('访问类变量法二:'+self.__class__.name)  #注意self.__class__的使用
            obj = Student('实例变量name','实例变量age')
        输出:
            形参name1:实例变量name
            修改实例变量:实例变量name
            访问类变量法一:类变量name
            访问类变量法二:类变量name

    --- 实例方法操作类变量 完成类变量的变化
        class Student():
            sum = 0
            name = '类变量name'   
            age = 0
            def __init__(self, name1, age): # 至少需要添加self参数
                self.__class__.sum += 1
                print('类变量sum变为:'+str(self.__class__.sum)) 
        obj1 = Student('Tom',13)
        obj2 = Student('Kimmy',24)
        obj3 = Student('Jack',18)
        输出:
            类变量sum变为:1
            类变量sum变为:2
            类变量sum变为:3   

        注意:
            实例方法通常是操作实例变量的,但是也可以操作类变量,引出:专门操作类变量的方法

类方法:

定义规范:
    @classmethod        #使用装饰器@classmethod来定义一个类方法
    def plus_sum(cls):  #类方法的参数必须含一个cls参数
        pass

重新完成上述类变量的修改:
    class Student():
        sum = 0
        def __init__(self,param): 
            pass
        @classmethod
        def plus_sum(cls): # cls仍然可以改成别的名字,不建议更改
            cls.sum += 1
            print(cls.sum)
类方法的调用
    stu = Student(1)
    stu.plus_sum()      # 打印 1
    stu = Student(2)
    stu.plus_sum()      # 打印 2
    stu = Student(3)
    stu.plus_sum()      # 打印 3

再次强调:
    实例方法关联的是对象,类方法关联的是类本身
    另外,两者有时候都可以完成参数修改,但是要是操作有“意义”有时就需要区分类方法和实例方法,例如与对象无关的操作就应该使用类方法
    即,对象最好不要调用@classmethod 类方法(虽然不报错,但是缺失实际意义)

静态方法

定义规范:
    @staticmethod
    def add(x,y):
        print("这是一个静态方法")
与其他方法的区别:
    静态方法中没有强制参数
    实例方法中,self 参数代表对象本身
    类方法中 cls 代表类本身
    一个对象或以各类都可以调用静态方法
示例:   
    class Student():
        sum = 0
        name ='Lei'
        def __init__(self,param): 
            self.name = param

        @classmethod
        def plus_sum(cls): 
            cls.sum += 1
            print(cls.sum)
            #print(self.name)    # 类方法不可以引用实例变量
        @staticmethod
        def add(x,y): 
            #print(self.name)            # 静态方法不可以引用实例变量
            print(Student.sum)              # 静态方法可以访问类变量
            print('这是一个静态方法')        
    #调用
    stu = Student(1)
    stu.add(1,1)        # 对象调用静态方法  且访问了类变量 不可以引用实例变量
    Student.add(1,1)    # 类调用静态方法   且访问了类变量 不可以引用实例变量
    stu.plus_sum()      # 对象调用类方法   且访问了类变量 不可以引用实例变量   
    Student.plus_sum()  # 类调用类方法   且访问了类变量 不可以引用实例变量

注意:
    静态方法不要经常使用,与类的关联性不强,与普通函数无区别

类成员的可见性

对于下面示例:
    class Student():
        sum = 0
        def __init__(self,param,param1): 
            self.name = param
            self.age = param1
            self.score = '0'
            print('初值为:'+self.score)

        def marking(self,score): 
            self.score = score
            print('修改后:'+str(score))

        def do_hmwork(self): 
            pass
        def do_eng_hmwork(self): 
            pass

    s = Student(1,2)    # 将socre参数隐藏,不暴露score的直接赋值
    s.score = -1        
    #不推荐方式,直接修改参数,这样没法进行相关过滤,不应该通过直接访问的方式修改
    print('修改后:'+str(s.score))

    #正确方法:所有访问应该通过方法操作变量,可以在方法中对输入进行判断,进而保护数据
    s1 = Student(1,2)
    s1.marking(-1)
注意:
    上述marking方法之外,仍然可以通过 s.score = -1 来直接赋值,
原因:
    上述变量和方法全部都是公开的

Python控制变量的可见性(读、写): 公开public 私有private

    方式:
        私有变量:__私有变量名
        私有函数:__marking()
    注意:
        对于构造函数,因为__init__右边也有下划线,这样不会被识别为私有

    示例:
        class Student():
            sum = 0
            def __init__(self,param,param1): 
                self.name = param
                self.age = param1
                self.__score = '0'
                print('初值为:'+self.__score)

            def __marking(self,score): #添加双下划线
                self.__score = score
                print('修改后:'+str(self.__score))
        s1 = Student(1,2)
        #s1.__marking(-1)      # 访问私有方法报错:'Student' object has no attribute '__marking'
        s1.__score = -1
        print(s1.__score)   # 访问私有变量,成功修改
        #上述原因是:实际是利用py得动态属性,通过点的方式新添加了一个__score变量,原有私有变量并没有修改
        #下面直接访问会发现 访问报错 :'Student' object has no attribute '__score'
        s2 = Student(1,2)
        print(s2.__score) 

    可以利用__dict__内容验证:
        print(s1.__dict__)
        输出:
            {'name': 1, '_Student__score': '0', 'age': 2,'_score':-1}
        print(s2.__dict__)
        输出:
            {'name': 1, '_Student__score': '0', 'age': 2}
        分析上述发现:
            其实私有变量会被改名,此处由__score变为了_Student__score,所以访问原名是访问不到的
            比较两次打印,会发现s1.__score = -1 这句话其实会添加一个__score变量,而没有修改原来的score。因为原来的socre已经被改名了
        上述发现:
            其实Python没有完善的私有变量机制,其仅仅是通过改名,如果使用_Student__score来操作,仍然可以完成修改

面向对象的特性:继承

三大特性:继承、封装、多态
封装:类就是从现实世界的角度对变量和方法进行封装,很抽象比较难讲清楚

类的组成:变量和方法
继承作用:避免定义重复的方法和重复的变量

推荐一个模块创建一个类

对于以下示例:
    c2模块的Human代码如下:
        class Human():
            sum = 0
            def __init__(self, name, age):
                self.name = name
                self.age = age
            def get_name(self):
                print(self.name)

    子类Student如下:
        from c2 import Human
        class Student(Human):  # 标准的继承方法
            sum = 0
            def __init__(self,name,age): 
                self.name = name
                self.age = age
                self.__score = 0
                print('初值为:'+str(self.__score))

        s = Student('Tom',13)  # 实例化子类时,要按照父类的构造函数传参
        print(s.sum)
        print(Student.sum)  # 打印 0 表示子类继承了父类的类变量
        print(s.name)   # 打印 Tom 表示子类继承了父类的实例变量
        print(s.age)    # 打印 13 表示子类继承了父类的实例变量
        s.get_name()   # 打印 Tom 表示子类继承了父类的实例方法
    注意:
        上述只是将Human父类的变量和方法提取到了子类中
        Python允许多继承,一个子类可以有多个父类,一般用不到
    进一步:
        现在子类有自己独有的方法和变量
        例如:Student类有school变量,那么其构造函数为school+父类构造参数


    在子类里调用父类的函数,示例:

        from c2 import Human
        class Student(Human):  # 标准的继承方法
            def __init__(self,school,name,age): # 父类构造参数是name age
                self.school = school
                Human.__init__(self,name,age)   #直接调用父类构造函数 传参
                #注意此处父类构造参数要加上self,此处是!普通函数的调用!,传参缺一不可,self必不可少
        s = Student('YangTz','Tom',13)
        print(s.school) # 正确打印YangTz
        print(s.name) # 正确打印Tom
        print(s.age)# 正确打印13

    开闭原则:
        对扩展是开放的,对更改本身是关闭的
    注意:
        Human.__init__(self,name,age)
        上述使用类,调用了实例方法,其实不推荐这样做,如果类调用一个实例方法,那么实例方法的 self 参数会成为一个普通参数,调用时应该被传入方法内
        现在对于上述代码,如果父类改变,那么代码中涉及的地方全都要改,违反了开闭原则

    引出:super() 通用调用方法,修改为:
        super(Student,self).__init__(name,age)
    注意:
        这样修改父类时不需要修改这里的代码
        super()目的是继承父类的同名方法,如__init__()或一些公共方法
        对于一个普通实例方法do_something(self),如果其和父类方法同名,那么会优先调用子类的此方法
        但是如果修改为
            def do_homework(self):
                super(Student,self).do_homework()
        那么此时表示子类的该实例方法继承了父类的该方法,此时调用会执行父类的do_something()