Python学习—面向对象编程基础篇
python面向对象编程已经学完了,有必要进行一下记录总结,加深一下理解,不然似懂非懂容易忘记;面向对象的知识点很多,其中面向对象的高级篇:元类,需要另外一篇博客来写,最后还要手撸一个简单版本的ORM框架。废话不多说,开始吧。
类的产生
类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体
在现实世界中:先有对象,再有类
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念;对象是具体的存在,而类仅仅只是一个概念,并不真实存在
在程序中:务必保证先定义类,后产生对象
定义类的关键字:class
面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同
用class关键字定义类
#python2中定义经典类class Foo: pass#python2中定义新式类class Foo1(object) pass#python3定义类class Foo2: pass
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
当前类或者父类继承了object,则为新式类,否则为经典类。由于python2中不会默认继承object类,所以要想在python2中定义新式类需要显示继承object类。python3默认都会继承object类,所以Python3中定义的类都是新式类。
类的名称空间
看以下类定义
class OldboyStudent: school='oldboy' def learn(self): print('is learning') def eat(self): print('is eating') def sleep(self): print('is sleeping')'''1.类中可以有任意python代码,这些代码在类定义阶段便会执行2.因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过OldboyStudent.__dict__查看3.对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的'.'语法4.点是访问属性的语法,类中定义的名字包括函数名,都是类的属性程序中类的用法“.”专门用来访问属性,本质操作的就是类的名称空间__dict__OldboyStudent.school #等于经典类的操作OldboyStudent.__dict__['school']OldboyStudent.school='Oldboy' #等于经典类的操作OldboyStudent.__dict__['school']='Oldboy'OldboyStudent.x=1 #等于经典类的操作OldboyStudent.__dict__['x']=1del OldboyStudent.x #等于经典类的操作OldboyStudent.__dict__.pop('x')#python为类内置的特殊属性类名.__name__# 类的名字(字符串)类名.__doc__# 类的文档字符串类名.__base__# 类的第一个父类(在讲继承时会讲)类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)类名.__dict__# 类的字典属性类名.__module__# 类定义所在的模块类名.__class__# 实例对应的类(仅新式类中)'''
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
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
此时关于类的操作都是直接以类名的方式进行操作,还未实例化一个具体的对象,如果安装以上类型定义实例化对象
s1=OldboyStudent()s2=OldboyStudent()s3=OldboyStudent()
- 1
- 2
- 3
- 1
- 2
- 3
这3个对象的属性就完全一样了,如果想要每个实例对象都不一样怎么办呢?这就要用到对象的初始化方法了。
类实例化对象
class OldboyStudent: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sexs1=OldboyStudent('李坦克','男',18) #先调用类产生空对象s1,然后调用OldboyStudent.__init__(s1,'李坦克','男',18),以下类似s2=OldboyStudent('王大炮','女',38)s3=OldboyStudent('牛榴弹','男',78)#程序中对象的用法#执行__init__,s1.name='牛榴弹',很明显也会产生对象的名称空间s2.__dict__{'name': '王大炮', 'age': '女', 'sex': 38}s2.name #s2.__dict__['name']s2.name='王三炮' #s2.__dict__['name']='王三炮's2.course='python' #s2.__dict__['course']='python'del s2.course #s2.__dict__.pop('course')#总结:类体中__init__方法是以:类名(),方式触发调用,__init__方法的第一个参数永远是self,#表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。#执行__init__方法时会产生对象的名称空间,且此方式不能有返回值#在创建实例的时候,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去#类的方法和普通函数没有什么区别,所以仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。#至此,产生了类的名称空间以及对象的名称空间,类的名称空间,实例对象可以访问,对象名称空间,通过类名不可访问
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
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
对象访问限制
为对象的私有属性增加访问限制
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score))'''如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private)只有内部可以访问,外部不能访问,'''
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果外部代码要获取name和score怎么办?可以给Student类增加get和set这样的方法
class Student(object): def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self, score): self.__score = score def set_score(self, score): self.__score = score'''需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是请把我视为私有变量,不要随意访问”双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是;不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以仍然可以通过_Student__name来访问__name变量'''#最后注意下面的这种错误写法:>>> bart = Student('Bart Simpson', 59)>>> bart.get_name()'Bart Simpson'>>> bart.__name = 'New Name' # 设置__name变量!>>> bart.__name'New Name''''表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。'''
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
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
继承与多态
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。子类会“”遗传”父类的属性,从而解决代码重用问题
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类 passclass ParentClass2: #定义父类 passclass SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass passclass SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类(<class '__main__.ParentClass1'>,)>>> SubClass2.__bases__(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)>>> ParentClass1.__bases__(<class 'object'>,)>>> ParentClass2.__bases__(<class 'object'>,)'''1.只有在python2中才分新式类和经典类,python3中统一都是新式类2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类3.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类'''
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承
基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
继承有什么好处?最大的好处是子类获得了父类的全部功能。
继承的第二个好处:多态
class Animal(object): def run(self): print('Animal is running...')class Dog(Animal): passclass Cat(Animal): pass
1
2
3
4
5
6
7
1
2
3
4
5
6
7
无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running…,符合逻辑的做法是分别显示Dog is running…和Cat is running…,因此,对Dog和Cat类改进如下:
class Dog(Animal): def run(self): print('Dog is running...')class Cat(Animal): def run(self): print('Cat is running...')
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。
要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样。
判断一个变量是否是某个类型可以用isinstance()判断:
>>> isinstance(a, list)True>>> isinstance(b, Animal)True>>> isinstance(c, Dog)True>>> isinstance(c, Animal)True>>> b = Animal()>>> isinstance(b, Dog)False'''因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是反过来就不行'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
def run_twice(animal): animal.run() animal.run()
- 1
- 2
- 3
- 1
- 2
- 3
当我们传入Animal的实例时,run_twice()就打印出:
>>> run_twice(Animal())Animal is running...Animal is running...
1
2
3
1
2
3
当我们传入Dog的实例时,run_twice()就打印出:
>>> run_twice(Dog())Dog is running...Dog is running...
- 1
- 2
- 3
- 1
- 2
- 3
当我们传入Cat的实例时,run_twice()就打印出:
>>> run_twice(Cat())Cat is running...Cat is running...
1
2
3
1
2
3
看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:
class Tortoise(Animal): def run(self): print('Tortoise is running slowly...')
- 1
- 2
- 3
- 1
- 2
- 3
当我们调用run_twice()时,传入Tortoise的实例:
>>> run_twice(Tortoise())Tortoise is running slowly...Tortoise is running slowly...
1
2
3
1
2
3
新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:
对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
class Timer(object): def run(self): print('Start...')
- 1
- 2
- 3
- 1
- 2
- 3
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。
对象属性查找顺序
类有两种属性:数据属性和函数属性
1. 类的数据属性是所有对象共享的
2. 类的函数属性是绑定给对象用的
#类的数据属性是所有对象共享的,id都一样print(id(OldboyStudent.school))print(id(s1.school))print(id(s2.school))print(id(s3.school))'''4377347328437734732843773473284377347328'''#类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样#ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准print(OldboyStudent.learn)print(s1.learn)print(s2.learn)print(s3.learn)'''<function OldboyStudent.learn at 0x1021329d8><bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>><bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>><bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>>在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
其实在Python解释器执行代码时,会根据类的继承关系创建一个列表,按照列表中对应的名称空间去进行属性查找,python2创建列表按照深度优先,Python3按照广度优先。
class A(object): def test(self): print('from A')class B(A): def test(self): print('from B')class C(A): def test(self): print('from C')class D(B): def test(self): print('from D')class E(C): def test(self): print('from E')class F(D,E): # def test(self): # print('from F') passf1=F()f1.test()print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性#新式类继承顺序:F->D->B->E->C->A#经典类继承顺序:F->D->B->A->E->C#python3中统一都是新式类#pyhon2中才分新式类与经典类
- 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
- 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
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
1
2
1
2
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
子类中调用父类的方法
方式1:
#_*_coding:utf-8_*___author__ = 'luke'class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...')class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): Vehicle.__init__(self,name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) Vehicle.run(self)line13=Subway('中国地铁','180m/s','1000人/箱','电',13)line13.run()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
方式2:
class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...')class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) super().__init__(name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) super(Subway,self).run()class Mobike(Vehicle):#摩拜单车 passline13=Subway('中国地铁','180m/s','1000人/箱','电',13)line13.run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
#A没有继承B,但是A内super会基于C.mro()继续往后找class A: def test(self): super().test()class B: def test(self): print('from B')class C(A,B): passc=C()c.test() #打印结果:from Bprint(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
super属性查找
#指名道姓class A: def __init__(self): print('A的构造方法')class B(A): def __init__(self): print('B的构造方法') A.__init__(self)class C(A): def __init__(self): print('C的构造方法') A.__init__(self)class D(B,C): def __init__(self): print('D的构造方法') B.__init__(self) C.__init__(self) passf1=D() #A.__init__被重复调用'''D的构造方法B的构造方法A的构造方法C的构造方法A的构造方法'''#使用super()class A: def __init__(self): print('A的构造方法')class B(A): def __init__(self): print('B的构造方法') super(B,self).__init__()class C(A): def __init__(self): print('C的构造方法') super(C,self).__init__()class D(B,C): def __init__(self): print('D的构造方法') super(D,self).__init__()f1=D() #super()会基于mro列表,往后找'''D的构造方法B的构造方法C的构造方法A的构造方法'''
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
获取对象信息
使用type()来判断对象类型
>>> type(123)<class 'int'>>>> type('str')<class 'str'>>>> type(None)<type(None) 'NoneType'>
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
如果一个变量指向函数或者类,也可以用type()判断:
>>> type(abs)<class 'builtin_function_or_method'>>>> type(a)<class '__main__.Animal'>
1
2
3
4
1
2
3
4
但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:
>>> type(123)==type(456)True>>> type(123)==intTrue>>> type('abc')==type('123')True>>> type('abc')==strTrue>>> type('abc')==type(123)False
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:
>>> import types>>> def fn():... pass...>>> type(fn)==types.FunctionTypeTrue>>> type(abs)==types.BuiltinFunctionTypeTrue>>> type(lambda x: x)==types.LambdaTypeTrue>>> type((x for x in range(10)))==types.GeneratorTypeTrue
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
使用isinstance()
对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
假设类的继承关系是:object -> Animal -> Dog -> Husky
那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:
>>> a = Animal()>>> d = Dog()>>> h = Husky()>>> isinstance(h, Dog)True>>> isinstance(h, Dog)True>>> isinstance(h, Animal)True>>> isinstance(d, Dog) and isinstance(d, Animal)True
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
能用type()判断的基本类型也可以用isinstance()判断:
>>> isinstance('a', str)True>>> isinstance(123, int)True>>> isinstance(b'a', bytes)True
1
2
3
4
5
6
1
2
3
4
5
6
并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))True>>> isinstance((1, 2, 3), (list, tuple))True
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
绑定方法
绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
1. 绑定到类的方法:用classmethod装饰器装饰的方法。
+ 为类量身定制
+ 类.boud_method(),自动将类当作第一个参数传入;
+ 其实对象也可调用,但仍将类当作第一个参数传入
2. 绑定到对象的方法:没有被任何装饰器装饰的方法。
+ 为对象量身定制
+ 对象.boud_method(),自动将对象当作第一个参数传入
+ 属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说
非绑定方法:用staticmethod装饰器装饰的方法
1. 不与类或对象绑定,
2. 类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
类的组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
1.继承的方式
+ 通过继承建立了派生类与基类之间的关系,它是一种’是’的关系,比如白马是马,人是动物。
+ 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3…
class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sexclass Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print('<%s %s %s>' %(self.name,self.period,self.price))class Teacher(People): def __init__(self,name,age,sex,job_title): People.__init__(self,name,age,sex) self.job_title=job_title self.course=[] self.students=[]class Student(People): def __init__(self,name,age,sex): People.__init__(self,name,age,sex) self.course=[]egon=Teacher('egon',18,'male','沙河霸道金牌讲师')s1=Student('牛榴弹',18,'female')python=Course('python','3mons',3000.0)linux=Course('python','3mons',3000.0)#为老师egon和学生s1添加课程egon.course.append(python)egon.course.append(linux)s1.course.append(python)#为老师egon添加学生s1egon.students.append(s1)#使用for obj in egon.course: obj.tell_info()
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
34
35
36
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
34
35
36
好啦,面向对象基础部分就写这么多,感觉面向对象要用3篇博客,第二篇应该是面向对象的高级部分反射与元类,第三篇就根据元类手写一个ORM框架,加深自己的理解。