类
一、对象的独有功能
我们之前讲的对象都有独有的数据那么怎么才能让对象有独有的功能或方法呢?
class Person: h_type = '人' # 公共的数据 def __init__(self): pass p1 = Person() print(p1.h_type) # 人 我们可以使用公共的功能 p1.name = 'jason' print(p1.name) # jason 我们也可以让对象有自己独有的数据 # 那怎么让对象有自己的功能呢? 我们可以直接定义一个函数 p1 = Person() def eat(): pass p1.eat = eat # 这样我们就让对象有了自己独有的功能 ''' 但是在全局中定义的话任何一个都可以调用这个功能这就不是独有的功能了 我们可以把功能放到类中 ''' class Person: h_type = '人' # 公共的数据 def __init__(self): pass def eat(self): print('正在干饭') p1 = Person() p1.eat() # 正在干饭 # 但是这样的话只要是这个类的对象都可以调用这个方法所以也不是独有的功能 ''' 其实对象的独有方法我们是没有办法实现的 1.在全局变量中 任何一个都可以调用 2.在类中定义 这个类的所有对象都可以调用 所以没有办法实现 所以python解释器给我们提供了非常强大的功能 就是在类中定义的函数是默认绑定给对象的(相当于是给对象独有的方法了) ''' class Person: h_type = '人' # 公共的数据 def __init__(self): pass def eat(self): print('正在干饭') # 我们可以看一下slef打印的是什么 p1 = Person() p1.eat() # 正在干饭 <__main__.Person object at 0x0000019FB9A19278> 其实就是对象本身 class Person: h_type = '人' # 公共的数据 def __init__(self, name): self.name = name def eat(self): print('%s正在干饭' % self.name) def other(self, a, b): print('from other') p1 = Person('jason') p1.eat() # jason正在干饭 # 就是在调用的时候把对象本身传入 就是相当于是对象的独有功能了 # 但是如果函数还有参数的话还需要传入参数才能运行 p1.other(1, 2) # from other
二、动静态方法
2.1绑定给对象的方法
直接在类体代码中编写即可
对象调用会自动将对象当做第一个参数传入
类调用则有几个参数就传几个参数
class Person: def __init__(self, name, age): self.name = name self.age = age p1 = Person('jason', 18) # 产生对象的时候会把对象当做第一个参数传入 所以只要在传两个实参即可 # print(p1.name, p1.age) # jason 18 # 而如果是类调用的话就是有几个参数就传几个参数 Person.__init__(p1, 'kevin', 28) print(p1.name, p1.age) # kevin 28 # 所以是专门给对象绑定的方法
2.2 绑定给类的方法
class Student: def __init__(self): pass @classmethod # 这个语法糖就是专门为类绑定的方法 def eat(cls): print('干饭', cls) stu1 = Student() Student.eat() # 干饭 <class '__main__.Student'> # 因为是类调用的所以不需要传入参数 会自动把类当做第一个传入 stu1.eat() # 干饭 <class '__main__.Student'> # 如果是对象调用就是把产生对象的类当做第一个参数传入 class Student: def __init__(self): pass @classmethod # 这个语法糖就是专门为类绑定的方法 def eat(cls,a,b): print('干饭', cls) stu1 = Student() Student.eat(1, 2) # 干饭 <class '__main__.Student'> # 如果定义函数的时候有形参那么我们就需要传入实参才可以运行
2.3 静态方法
class Student: def __init__(self): pass @classmethod def eat(cls): print('干饭', cls) @staticmethod # 静态方法 普普通通的函数无论谁调用都需要传参 除非没有形参 def sleep(a, b): print('正在睡觉', a, b) stu1 = Student() stu1.sleep(1, 2) # 正在睡觉 1 2 对象调用静态方法的函数有几个参数就传几个参数 Student.sleep(1, 2) # 正在睡觉 1 2 类调用静态方法亦是如此
三、面向对象三大特性之继承
面向对象有三大特性:
继承、封装、多态
其中又以继承最重要
3.1 继承的含义
在现实社会中继承其实就是用来描述人与人之间资源的关系
eg: 儿子继承了爹的家产(儿子就有了爹的资源)
而在编程世界中的基础就是要来描述类与类之间数据的关系
eg: 类A继承了类B(类A就有了类B中的所有数据和功能)
3.2继承的目的
现实生活中继承就是想占有别人的家产(多认几个干爹)
eg:亲身父亲 干爹 干妈 富婆
编程世界里继承就是为了节省代码编写
eg:可以继承一个类 也可以继承多个类
3.3继承的代码实现
class 类名(继承的类名): 类体代码 ''' 1.定义类的时候在类名后面加括号 2.括号内写上继承的类名 3.一个类能够继承多个类 括号内类名与类名用逗号隔开 ''' ''' 我们将被继承的类叫做>>>父类或基类或超类 我们将继承类的类叫做>>>子类或派生类 ''' class MyClass(A,B,C): pass
四、继承的本质
class Teacher: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def teacher_course(self): print('老师正在上课') class Student: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def student_course(self): print('学生正在选课') ''' 现在这两个类因为都有共同的数据比如姓名、年龄、性别 但是如果在定义其他类时还需要这些数据我们完全可以把相同的数据写到一个类中 然后其他类继承这个类即可 ''' class Teacher: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def teacher_course(self): print('老师正在上课') class Student(Teacher): def student_course(self): print('学生正在选课') stu1 = Student('tony', 18, 'male') print(stu1.__dict__) # {'name': 'tony', 'age': 18, 'gender': 'male'} # 这样我们就可以节省代码了 ''' 抽象:就是将多个类共同的数据或功能结合出来形成一个基类 继承:从上往下白嫖各个基类的资源 对象:数据和功能的集合体 类:多个对象的数据和功能的集合体 父类:多个类的数据和功能的集合体 ps:类和父类主要功能就是为了就是代码 '''
要找出类与类之间的继承关系, 首先我们要抽象, 再继承。抽象就是把每个对象结合相似之处然后得到类,总结类与类之间的相似之处的到父类
然后我们就可以找到继承的关系了
五、名字的查找顺序
既然类可以继承那么类和对象的名字找找顺序有事什么呢?
5.1不继承情况下的查找顺序
class Student: school = '清华大学' stu1 = Student() print(stu1.school) # 清华大学 print(Student.school) # 清华大学 # 当如果对象中没有想要的名字的时候就会去产生对象的类中查找 stu1.school = '北京大学' print(Student.school) # 清华大学 因为是修改的对象本身的内存空间里的值所以类中的名字没有被改变 print(stu1.school) # 北京大学 # 因为上面那行代码相当于往对象中产生了一对新键值对 所以当对象中有想要的名字时就不用在取类中查找了 ''' 对象 >>> 类 '''
5.2单继承情况下的查找顺序
class A: name = 'from A' class B: name = 'from B' class C: name = 'from C' class MyClass(A,B,C): name = 'from MyClass' obj = MyClass() obj.name = 'form obj' print(obj.name) # form obj # 单继承 也是从对象开始查找名字 class A: name = 'from A' class B: name = 'from B' class C: name = 'from C' class MyClass(A,B,C): name = 'from MyClass' obj = MyClass() print(obj.name) # from MyClass # 如果对象中没有就会去产生对象的类中查找 class A: name = 'from A' class B: name = 'from B' class C: name = 'from C' class MyClass(A,B,C): # name = 'from MyClass' pass obj = MyClass() print(obj.name) # from A # 如果类中没有就会去继承的类查找 从左往右依次查找 直到找到为止 如果都没有就会报错 # 对象 >>> 类 >>> 父类
5.2.1 小练习
class A1: def func1(self): print('from A1 func1') def func2(self): print('from A1 func2') self.func1() # obj.func1() class MyClass(A1): def func1(self): print('from MyClass func1') obj = MyClass() # 首先产生一个对象 obj.func2() # 因为MyClass继承了A1 所以对象可以调用A1下的方法 ''' 打印结果如下: from A1 func2 from MyClass func1 1.首先产生一个对象 2.因为MyClass继承了A1 所以对象可以调用A1下的方法 3.首先对象和MyClass中没有func2名字所以去A1中查找 4.运行func2函数就会打印 然后调用func1 5.这个时候查找func1就又会重新开始从对象查找 6.因为MyClass中有func1的名字 所以直接打印 不用在去A1查找了 '''
所以我们一旦遇到对象查找名字 几乎是从最开始位置依次查找的
5.3.多继承的情况下查找顺序
多继承的情况有两种情况
5.3.1 非菱形继承
就是按照下图的顺序查找
最后不会归总到我们自定义的一个类上
深度优先(一条分支走到底 然后在切换另一个分支)
如下图:
5.3.2 菱形继承
就是按照下图的查找顺序
最后会归总与我们自定义的类上
广度优先(前面的分支不会直接走到最后一个类上 最后一个分支才会走到最后一个类上)
如下图:
5.3.3 mro方法
我们可以通过类点mro()方法查看名字的查找顺序
class G: name = 'from G' class D(G): name = 'from D' class E(G): name = 'from E' class F(G): name = 'from F' class A(D): name = 'from A' class B(E): name = 'from B' class C(F): name = 'from C' class MyClass(A,B,C): name = 'from MyClass' obj = MyClass() obj.name = 'form obj' print(obj.name) # form obj print(MyClass.mro()) ''' 结果是一个列表 任何一个对象查找名字的顺序都可以通过mro方法 [<class '__main__.MyClass'>, <class '__main__.A'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.G'>, <class 'object'>] '''
'''
##############################
我们只要记得涉及对象查找名字的顺序 那么几乎都是
对象本身 >>> 类 >>> 父类
#############################
'''
六、经典类和新式类
6.1 经典类和新式类的区别
''' 经典类 不继承object或其子类的类(什么都不继承) 新式类 继承了object或其子类的类 在python3中所有的类都会默认继承object 也就意味着python3都是新式类 而在python2中有经典类和新式类的区分 由于经典类没有核心功能 所以到python3中就把它删了 '''
6.2 python2与python3的区别
# python2 :
# class MyClass: # pass # print MyClass.__bases__ # () 在python中如果是经典类的话 名称空间里是什么都没有的 class MyClass(object): pass print MyClass.__bases__ # (<type 'object'>,) 而想要有数据和方法必须继承object才行 要不然啥用没有
# pyhon3 : class MyClass(object): pass print(MyClass.__bases__) # (<type 'object'>,) 而想要有数据和方法必须继承object才行 要不然啥用没有 # 而在python3中上述编写等同于下方的编写 class MyClass: pass print(MyClass.__bases__) # (<class 'object'>,)
'''
所以我们以后在编写类时 如果没有继承的父类 最好在后面继承上object
class MyClass(object):
pass
这样能够兼容python2和python3
'''
七、派生
7.1 spuer()
class Person: def __init__(self, name, age): self.name = name self.age = age ''' 现在我们把老师类和学生类都继承了Person类 我们可以有Person的共同功能 可是现在我们想要让 老师类拥有自己的数据eg:薪资、等级... 学生类拥有自己的数据eg:成绩、身高... ''' # 我们可以这样编写 class Teacher(Person): def __init__(self, name, age, salary, level): Person.__init__(self,name, age,) # 调用父类的方法 self.salary = salary # 自己填充的方法 self.level = level # 这样我们就可以让老师类既有公共的数据 也有自己的功能了
# 不过python解释器给我们提供了专门运用于这个的方法 spuer() class Teacher(Person): def __init__(self, name, age, salary, level): super().__init__(name, age,) # 调用父类的方法 self.salary = salary # 自己填充的方法 self.level = level # super是专门用于子类调用父类的方法 class Student(Person): def __init__(self, name, age, score): super().__init__(name, age) self.score = score # 学生类也是运用了super的方法 ''' 而这种方法就是叫做派生 其实就是一个类继承了父类 子类可以用到父类的所有功能 但是我还想要添加新功能 就可以用到派生
关键字: super() '''
7.2 小小练习题
# 学会派生之后我们可以有很多的操作: # eg:现在我们有一个列表什么都可以添加就是不让'tony'添加 class MyClass(list): def append(self, value): if value == 'tony': print('tony 不能添加') return super().append(value) l1 = MyClass() l1.append('tony') # tony 不能添加 l1.append(1) l1.append(2) print(l1) # [1, 2] # 这样我们就可以实现上述功能