摘要
本文内容转自网络,个人学习记录使用,请勿传播
面向对象
面向过程VS面向对象
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的思想就好比是精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线就是用来解决一个问题,代码牵一发而动全身。
应用场景:
- 一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,并且一个对象包含数据和操作数据的方法。
面向对象的程序设计的核心是对象,要理解对象为何物,必须把自己当成上帝(上帝式思维)。上帝眼里世间存在的万物皆为对象。
形象化场景设计:
- 面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,”如来”想了想解决这个问题需要四个人(对象):唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法)。然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。“如来”根本不会管师徒四人按照什么流程去取,只关心最后结果是否可以实现。
- 因此面向对象的核心思想就是使用一个有一个的对象来完成某件具体是事件,且不用关心完成的具体过程!
面向对象的优点:面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使他人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
- 应用场景:需求经常变化的软件,如互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
类和实例
类,英文名字Class,有“类别”,“分类”,“聚类”的意思。
必须牢记类是抽象的模板,用来描述具有相同属性和方法的对象的集合,比如Animal类。
而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
Python使用class关键字来定义类,其基本结构如下:
1
2class 类名(): #一般类名首字母是大写
pass下面是一个学生类:
1
2
3
4
5
6
7
8
9
10
11class Student():
#数据
classroom = '101'
address = 'beijing'
#构造方法
def __init__(self, name, age):
self.name = name
self.age = age
#操作
def print_age(self):
print('%s: %s' % (self.name, self.age))对象的创建
可以通过调用类的实例化方法(有的语言中也叫初始化方法或构造函数)来创建一个类的实例(对象)。
Python提供了一个
def __init__(self):
的实例化机制。任何一个类中,名字为__init__
的方法就是类的实例化方法,具有__init__
方法的类在实例化的时候,会自动调用该方法,并传递对应的参数。1
2zhangsan = Student('zhangsan',20)
lisi = Student('lisi',30)
实例变量和类变量
实例变量
实例变量指的是实例(对象)本身拥有的变量。Student类中
__init__
方法里的name和age就是两个实例变量。通过实例名加圆点的方式调用实例变量(可以通过对象名打点的方式去调用/访问属于对象的成员)。
1
2
3
4
5
6
7
8
9
10
11
12class Student():
#init称为构造方法
def __init__(self,i_name,i_age):
#只要定义在init方法内部的变量就是【实例/对象变量】
self.name = i_name #self.name就是定义的实例变量,name是init方法的参数值
self.age = i_age #self.age就是定义的实例变量,age就是init方法的参数值
s1 = Student('zhangsan',20) #调用Student类中的init这个构造方法
s2 = Student('lisi',25)
#根据对象的引用访问对象的实例变量
print(s1.name,s1.age) #访问s1对象的name和age这两个实例变量
print(s2.name,s2.age) #访问s2对象的name和age这两个实例变量
类变量
定义在类中,方法之外的变量,称作类变量。类变量是所有实例公有的变量,每一个实例都可以访问类变量。
在Student类中,classroom和address两个变量就是类变量。可以通过类名或者实例名加圆点的方式访问类变量,比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Student():
#定义在方法外部的变量:类变量
address = 'Beijing'
classroom = 167
#init称为构造方法
def __init__(self,i_name,i_age):
#只要定义在init方法内部的变量就是【实例/对象变量】
self.name = i_name #self.name就是定义的实例变量,name是init方法的参数值
self.age = i_age #self.age就是定义的实例变量,age就是init方法的参数值
s1 = Student('zhangsan',20) #调用Student类中的init这个构造方法
s2 = Student('lisi',25)
#根据对象的引用访问对象的实例变量
print(s1.name,s1.age) #访问s1对象的name和age这两个实例变量
print(s2.name,s2.age) #访问s2对象的name和age这两个实例变量类变量的特性:
所有的类变量是可以通过类名或者对象名打点的方式访问/调用的。
1
2
3print(s1.address,s1.classroom) #通过对象名可以访问类变量(不推荐)
#通过类名可以访问类变量(推荐)
print(Student.address,Student.classroom)类变量是可以被所有的对象公用的
1
2print(s1.address,s1.classroom) #通过s1对象访问类变量
print(s2.address,s2.classroom) #通过s2对象访问类变量
思考:如何修改类变量中存储的数据?
通过对象名访问类变量对其进行内容的修改?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Student():
#定义在方法外部的变量:类变量
address = 'Beijing'
classroom = 167
#init称为构造方法
def __init__(self,i_name,i_age):
#只要定义在init方法内部的变量就是【实例/对象变量】
self.name = i_name #self.name就是定义的实例变量,name是init方法的参数值
self.age = i_age #self.age就是定义的实例变量,age就是init方法的参数值
s1 = Student('zhangsan',20) #调用Student类中的init这个构造方法
s2 = Student('lisi',25)
#想要通过s1对象访问address类变量,且给其修改内容
s1.address = 'SH'
print(s2.address)
#思考:类变量是被所有的对象共享,因此通过s1对象修改了类变量,s2再次访问类变量,情况如何?
#s2访问的类变量address依然是beijing不是s1修改后的上海,Why?
#这种情况是说明类变量不是被所有对象共享的吗?一定不是这样!特别注意:建议大家使用类名访问类变量。如果通过对象名直接访问类变量是没有问题的。但是通过对象名访问类变量后给其进行赋值操作意图修改类变量实则发生的是通过对象打点的方式给对象动态的新增了一个实例变量,并不是在修改类变量。
1
2s1.address = 'SH' #动态的给s1这个变量新增了一个实例变量,这个实例变量叫做address。该操作执行完毕后,则s1这个对象内部多了一个实例变量叫做s1,且s1对象对应的类中也有一个address表示的类变量。
print(Student.address)#在访问类变量address,这个类变量依然还是Beijing。
通过实例名修改?
1 | #在访问类变量 |
注意:
1 | #对象创建好了之后,是可以通过对象名动态的给对象添加新的实例变量。对象添加了实例变量后,只会影响该对象本身,不会对类和其他对象造成影响。 |
测试:
1
2
3
4
5
6
7
8
9
10
11
12class Student():
address = 'Beijing'
#构造方法:用来实例变量初始化赋值
def __init__(self,addr):
#构造方法中也可以写入其他操作,但是一般情况下,只需要在构造方法中写给实例变量初始化赋值的操作。
self.address = addr
#实例变量名和类变量名一致
s = Student('SH') #创建对象其实就是在调用类中的init方法
#如果实例变量和类变量同名,通过对象名访问,优先方位对象的实例变量
print(s.address) #输出:SH
print(Student.address)#输出:Beijing
类的方法
Python的类中包含实例方法、静态方法和类方法三种方法。区别在于传入的参数和调用方式不同。
在类的内部,使用def
关键字来定义一个方法。
实例方法
类的实例方法由实例调用,至少包含一个self参数,且为第一个参数。执行实例方法时,会自动将调用该方法的实例赋值给self。
self
代表的是类的实例,而非类本身。self
不是关键字,而是Python约定成俗的命名,你完全可以取别的名字,但不建议这么做。例如,我们前面Student类中的print_age()就是实例方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Student():
classroot = 167 #类变量
#构造方法
def __init__(self,name,age):
#实例变量
self.name = name
self.age = age
#实例方法:self不是python的关键字,实例方法的第一个参数也可以叫其他的名字,但是约定俗成叫做self。
#注意:实例方法只可以通过对象调用。
def study(self,book):#self是不需要手动给其传值
print('正在学习的书籍是:',book)
s = Student('zhangsan',20) #调用构造方法
#只给除了self其他的参数传值
s.study('高等数学')实例方法中的第一个参数self到底是什么鬼?
- 想要在一个实例方法内部调用另一个实例方法?
- 核心:实例方法只可以被对象调用
- 想要在一个实例方法内部调用另一个实例方法?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Student():
classroot = 167 #类变量
#构造方法
def __init__(self,name,age):
#实例变量
self.name = name
self.age = age
#实例方法
def study(self,book): #self就是study方法的调用者(对象)
#注意:在study方法内部调用play方法,如何实现?
self.play('足球') #实例方法必须使用对象调用
#self表示的就是调用该方法的对象的引用
print('正在学习的书籍是:',book)
#实例方法
def play(self,b):
print('正在玩的项目是:',b)
s = Student('zhangsan',20) #调用构造方法
s.study('高等数学')- self就是study方法的调用者(对象),self表示的就是调用该方法的对象的引用
对象之间的交互:设计人狗大战游戏,让他们真正的打一架。
1 | class Person(): |
静态方法
静态方法由类调用,无默认参数。将实例方法参数中的self去掉,然后在方法定义上方加上@staticmethod,就成为静态方法。
静态方法属于类,和实例无关。建议只使用类名.静态方法的调用方式。(虽然也可以使用实例名.静态方法的方式调用)
1
2
3
4
5
6
7
8
9
10
11
12
13class Obj():
def __init__(self):
pass
#定义一个静态方法
def staticFunc(name):#静态方法不需要有任何的必要参数
print('我是静态方法!,我有一个普通参数:',name)
Obj.staticFunc('bobo') #通过类名调用(推荐)
o = Obj()
o.staticFunc('bobo') #通过对象名调用(不推荐)
#静态方法既不属于类也不属于对象,仅仅是写在类内部的一个普通函数而已
类方法
类方法由类调用,采用@classmethod装饰,至少传入一个cls(代指类本身,类似self)参数。
执行类方法时,自动将调用该方法的类赋值给cls。建议只使用类名.类方法的调用方式。(虽然也可以使用实例名.类方法的方式调用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Obj():
f = 'classVar' #类变量
def __init__(self):
pass
def classFunc(cls): #类方法必须要有一个cls的参数,且作为第一个参数
#cls也不是python的关键字,cls也可以写作其他的形式,比如:name,self
print('我是类方法!必要参数cls的值为:',cls)
print('类变量的值为:',cls.f) #类名访问类变量
#cls表示的是当前类
o = Obj()
o.classFunc() #通过对象名访问(不推荐)
Obj.classFunc() #通过类名访问(推荐)测试题:
- 在类方法中是否可以调用实例方法和实例变量?
- 不能,因为实例变量和实例方法只可以通过对象名访问,但是在类方法内部不存在对象名。
- 在实例方法中是否可以调用类方法和类变量?
- 可以,但是不建议这么做!
- 在类方法中是否可以调用实例方法和实例变量?
面向对象的组合用法
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
思路设计:一个学生会有一步手机,学生使用手机看电影。
1 | class Student(): |
封装、继承和多态
面向对象有三大特性:封装、继承和多态
封装
封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现且不能通过任何形式修改对象内部实现,正是由于封装机制。
作用:
- 程序在使用某一对象时不需要关心该对象的数据结构细节及实现操作的方法。使用封装能隐藏对象实现细节,使代码更易维护,同时因为不能直接调用、修改对象内部的私有信息,在一定程度上保证了系统安全性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Student:
classroom = '101'
address = 'beijing'
def __init__(self, name, age):
self.name = name
self.age = age
def print_age(self):
print('%s: %s' % (self.name, self.age))
# 以下是错误的用法
# 类将它内部的变量和方法封装起来,阻止外部的直接访问
print(classroom)
print(adress)
print_age()
继承
继承来源于现实世界:
- 一个最简单的例子就是孩子会具有父母的一些特征,即每个孩子都会继承父亲或者母亲的某些特征,当然这只是最基本的继承关系,现实世界中还存在着更复杂的继承。
在OOP程序设计中,当我们定义一个新类的时候,新的类称为子类(Subclass),而被继承的类称为基类、父类或超类(Base class、Super class)。
继承最大的好处是子类获得了父类的全部变量和方法的同时,又可以根据需要进行修改、拓展。其语法结构如下:
1
2class Foo(superA, superB,superC....):
passPython支持多父类的继承机制。
继承示例代码:子类可以继承到父类中所有的成员
1 | class Father(): |
派生
- 子类添加自己独有的方法和或属性
1 | class Father(): |
方法的重写
重写:子类继承到父类的方法,如果满足不了子类的需求,则子类可以重写从父类中继承到的方法。重写父类方法有两种方式:1完全重写,2部分重写
1.完全重写:完全重新将父类的方法进行的全新的定义/实现(毫无保留父类方法原始的功能)
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Father():
def __init__(self,firstName):
self.firstName = firstName
def hobby(self):
print('我喜欢读书,运动和跳舞!')
class Son(Father):
#完全重写
def hobby(self):
print('我喜欢吃鸡,王者!')
s = Son('zhang')
s.hobby()
2.部分重写:在父类方法功能实现的基础上新增了其他操作/功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Father():
def __init__(self,firstName):
self.firstName = firstName
def hobby(self):
print('我喜欢读书,运动和跳舞!')
class Son(Father1):
#部分重写
def hobby(self):
#调用一下父类的hobby方法
super().hobby() #调用父类的方法
print('我喜欢吃鸡,王者!')
s = Son('zhang')
s.hobby()
super()只可以作用在类的内部,然后表示的是父类对象的引用。
super函数
如果你想强制调用父类的成员该如何实现呢?使用super()函数!这是一个非常重要的函数,最常见的就是通过super调用父类的实例化方法
__init__
!语法:
super(子类名, self).方法名()
,需要传入的是子类名和self,调用的是父类里的方法,按父类的方法需要传入参数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Father():
def __init__(self,firstName):
self.firstName = firstName
def hobby(self):
print('我喜欢读书,运动和跳舞!')
class Son(Father):
#子类需要有属于自己的实例变量
def __init__(self,firstName,classroom,score):
super().__init__(firstName)
#子类自己派生出的独有的实例变量
self.classroom = classroom
self.score = score
#部分重写
def hobby(self):
#调用一下父类的hobby方法
super().hobby() #调用父类的方法
print('我喜欢吃鸡,王者!')
s = Son('zhang',102,100)
s.hobby()
继承的作用:
- 实现了程序的高复用,大大缩短程序的开发周期!
在多继承中,继承关系的优先级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Father1():
def hobby(self):
print('我是Father1,我喜欢唱歌!')
class Father2():
def hobby(self):
print('我是Father2,我喜欢跳舞!')
#注意:越靠前的父类越优先被继承
class Son(Father1,Father2):
#方法的重写
def hobby(self):
#思考:现在Son有两个父类,super()表示的是哪一个父类对象?
#输出结果显示super表示的是Father1对象
super().hobby()
print('我是Son,我喜欢吃鸡!')
s = Son()
s.hobby()
type和isinstance
isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。
isinstance() 方法的语法:
1
2
3
4
5
6isinstance(object, classinfo)
参数:
object -- 实例对象。
classinfo -- 可以是类名、基本类型或者有它们组成的元组。
返回值:
如果对象的类型与参数二的类型(classinfo)相同则返回 True,否则返回 False。1
2
3
4
5
6
7
8class Father():
pass
class Son(Father):
pass
s = Son() #子类对象
print(isinstance(s,Son)) #True
#s这个对象是属于Son这个数据类型的。
#python的数据类型:内置类型(8中),自定义类型(类)1
2
3
4
5
6
7class Father():
pass
class Son(Father):
pass
s = Son() #子类对象
print(isinstance(s,Father)) #True
#注意:子类对象可以被视为是一种父类类型1
2
3
4
5
6class Father():
pass
class Son(Father):
pass
s = Son() #子类对象
print(type(s)) #<class '__main__.Son'>
type:返回对象的类型
isinstance() 与 type() 区别:
type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。
继承注意事项
实例方法中的self参数是一个相对的值?
1
2
3
4
5
6
7
8
9class Father():
def get_xxx(self):
print(self)
class Son(Father):
pass
f = Father()
f.get_xxx()
#输出:<__main__.Father object at 0x103536580>
#说明self表示的是父类类型的对象1
2
3
4
5
6
7
8
9class Father():
def get_xxx(self):
print(self)
class Son(Father):
pass
s = Son()
#是子类对象从父类中继承过来,self原本是写在父类的实例方法中的
s.get_xxx()
#输出结果:<__main__.Son object at 0x100e2a880>- self所处的位置不同,表示的对象类型也是不同的!
类方法中的cls参数是一个相对的值?
1
2
3
4
5
6
7
8
9
10
11
12class Father():
#类变量
class_var = 'father var'
def getVar(cls):
print(Father.class_var)
class Son(Father):
#类变量:和父类的类变量同名,子类同名的类变量就会覆盖从父类中继承过来的类变量
class_var = 'son var'
Son.getVar() #输出:father var1
2
3
4
5
6
7
8
9
10
11
12class Father():
#类变量
class_var = 'father var'
def getVar(cls):
print(cls.class_var)
class Son(Father):
#类变量:和父类的类变量同名,子类同名的类变量就会覆盖从父类中继承过来的类变量
class_var = 'son var'
Son.getVar() #输出:son var- 注意:cls存在哪个类中,就表示的是当前的类。
类方法的作用
思考:实例方法中的self参数的值是从哪来的?
1 | Python 中的__new__和__init__的区别: |
类方法
如下场景:
假设我有一个学生类和一个班级类,想要实现的功能为:
学生类继承自班级类,每实例化一个学生对象,班级人数都能增加; 最后,我想定义一些学生,获得班级中的总人数。
- 要求:
- 必须通过班级类获取所有的学生对象的个数
- 不能创建班级对象,只能创建学生对象
- 思路:
- 需要在班级类中定义一个变量,让该变量记录学生的人数(每创建好一个学生对象则让班级类中的该变量进行加1操作),那么该变量如何创建(类变量or实例变量)?
- 应该创建类变量:上述要求明确指明了,不可以创建班级对象,因此无法访问实例变量,所以应该创建一个类变量。
- 如果班级类中有一个类变量,该类变量记录学生的个数,需要给该类变量提供一个访问的接口,该接口应该使用类方法getNum来实现。类方法可以直接被班级类名访问。
- 当每创建好一个学生对象,如何同步使得班级类中的类变量num进行加一操作呢?
- 在班级类中提供一个类方法addNum,该类方法可以给num类变量进行加一操作。该方法必须保证,当创建好一个学生对象的时候被调用一次。
- 如何保证每创建好一个学生对象让addNum这个类方法调用一次呢?
- 需要在班级类中重写new方法(该new方法是可以继承给学生这个子类),该new方法可以继承给Student子类,因此每创建一个子类对象,就会调用一次new方法,则在new方法中就可以执行给num类变量进行加一操作。
- 需要在班级类中定义一个变量,让该变量记录学生的人数(每创建好一个学生对象则让班级类中的该变量进行加1操作),那么该变量如何创建(类变量or实例变量)?
- 要求:
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
26class ClassRoom():
num = 0 #记录学生对象的个数
#类方法是用来显示计数器num的数值
def getNum(cls):
print(cls.num)
#是用来给num进行加一操作,该方法必须保证,当创建好一个学生对象的时候被调用一次
def addNum(cls):
cls.num += 1
#new最终是可以被继承给Student这个子类
def __new__(cls, *args, **kwargs):
ClassRoom.addNum()
return super().__new__(cls)
class Student(ClassRoom):
def __init__(self,name):
self.name = name
s1 = Student('zhangsan')
s2 = Student('lisi')
s3 = Student('wangwu')
s4 = Student('bobo')
ClassRoom.getNum()
成员保护和访问限制
在类的内部,有各种变量和方法。这些数据成员,可以在类的外部通过实例或者类名进行调用,例如:
1 | class Student(): |
上面的调用方式是我们大多数情况下都需要的,但是往往我们也不希望所有的变量和方法能被外部访问,需要针对性地保护某些成员,限制对这些成员的访问。这样的程序才是健壮、可靠的,也符合业务的逻辑。
在Python中,如果要让内部成员不被外部访问,可以在成员的名字前加上两个下划线__,这个成员就变成了一个私有成员(private)。私有成员只能在类的内部访问,外部无法访问。
1 | class Student(): |
思考:私有成员是否可以被子类继承?
1
2
3
4
5
6
7
8
9
10
11class Person():
def __init__(self,p):
#私有成员
self.__p = p
class Student(Person):
def get_p(self):
print(self.__p)
s = Student(1)
print(s.get_p()) #无法访问私有成员
#程序执行会报错!- 注意:私有成员是无法被继承的。
那么,以双下划线开头的数据成员是不是一定就无法从外部访问呢?其实也不是!本质上,从内部机制原理讲,外部不能直接访问__age
是因为Python解释器对外把__age
变量改成了_People__age
,也就是_类名__age
(类名前是一个下划线)。因此,投机取巧的话,你可以通过_ People__age
在类的外部访问__age
变量:
1 | class Person(): |
也就是说:Python的私有成员和访问限制机制是“假”的,没有从语法层面彻底限制对私有成员的访问。这一点和常量的尴尬地位很相似。
reflect反射
首先,我们要区分两个概念——“标识名”和“字符串”。两者字面上看起来一样,却是两种东西,比如下面的func函数和字符串“func”:
1 | def func(): |
前者是函数func的函数名,后者只是一个叫“func”的字符串,两者是不同的事物。我们可以用func()的方式调用函数func,但我们不能用”func”()的方式调用函数。说白了就是,不能通过字符串来调用名字看起来相同的函数!
实例分析
考虑有这么一个场景:需要根据用户输入url的不同,调用不同的函数,实现不同的操作,也就是一个WEB框架的url路由功能。
首先,有一个commons.py文件,它里面有几个函数,分别用于展示不同的页面。
1 | # commons.py |
其次,有一个visit.py文件,作为程序入口,接收用户输入,并根据输入展示相应的页面
1 | # visit.py |
运行visit.py,输入home,页面结果如下:
1 | 请输入您想访问页面的url: home |
这就实现了一个简单的url路由功能,根据不同的url,执行不同的函数,获得不同的页面。
然而,让我们思考一个问题,如果commons文件里有成百上千个函数呢(这很常见)?难道在visit模块里写上成百上千个elif?显然这是不可能的!那么怎么办?
仔细观察visit.py中的代码,会发现用户输入的url字符串和相应调用的函数名好像!
如果能用这个字符串直接调用函数就好了!但是,前面已经说了字符串是不能用来调用函数的。为了解决这个问题,Python提供了反射机制,帮助我们实现这一想法!
现在将前面的visit.py修改一下,代码如下:
1 | # visit.py |
func = getattr(commons,inp)
语句是关键,通过getattr()函数,从commons模块里,查找到和inp字符串“外形”相同的函数名,并将其返回,然后赋值给func变量。变量func此时就指向那个函数,func()就可以调用该函数。
getattr()函数的使用方法:接收2个参数,前面的是一个类或者模块,后面的是一个字符串,注意了!是个字符串!
这个过程就相当于把一个字符串变成一个函数名的过程。这是一个动态访问的过程,一切都不写死,全部根据用户输入来变化。
瑕疵:前面的代码还有个小瑕疵,那就是如果用户输入一个非法的url,比如jpg,由于在commons里没有同名的函数,肯定会产生运行错误
那怎么办呢?python提供了一个hasattr()的内置函数,用法和getattr()基本类似,它可以判断commons中是否具有某个成员,返回True或False。现在将代码修改一下:
1 | # visit.py |
这下就没有问题了!通过hasattr()的判断,可以防止非法输入导致的错误,并将其统一定位到错误页面。
单例模式
单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于被外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
应用场景
比如,某个服务器的配置信息存在在一个文件中,客户端通过AppConfig类来读取配置文件的信息.如果程序的运行的过程中,很多地方都会用到配置文件信息,则就需要创建很多的AppConfig实例,这样就导致内存中有很多AppConfig对象的实例,造成资源的浪费.其实这个时候AppConfig我们希望它只有一份,就可以使用单例模式.
1 | class Singleton(): |
链表
链表就是用来修正/优化列表存在的某一个弊端的。
列表存在的弊端:
- 列表开启的内存空间是连续的,导致如果向列表中增加或者删除元素,需要将增加或者删除元素后面的所有元素批量移动,该行为会严重影响列表元素添加或者删除的性能。
- 列表是不适合进行高频的元素添加和删除操作的。
分析:
- 链表如何进行列表弊端的优化?
- 优化核心在于,元素的内存空间不可以连续开辟,因此链表的内存空间不是连续开辟,则意味着链表就丧失了索引的机制。
- 链表的作用:
- 如果需求需要进行高频的元素增加和删除,则使用链表最合适。
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91class Node():
def __init__(self,item):
self.item = item
self.next = None
class Link():
def __init__(self):
#head指向第一个节点
self.head = None
def addHead(self,item): #向链表头部插入新的节点
node = Node(item)
node.next = self.head
self.head = node
def travel(self): #遍历链表中节点的item
# print(self.head.item)
# print(self.head.next.item)
# print(self.head.next.next.item)
# print(self.head.next.next.next.item)
# print(self.head.next.next.next.next.item)
cur = self.head
while cur != None:
print(cur.item)
cur = cur.next
def isEmpty(self):
#判定链表是否为空,空返回True,否则返回False
return self.head == None
def length(self):
#返回链表中节点的个数
count = 0 #记录节点的个数
cur = self.head
while cur != None:
count += 1
cur = cur.next
return count
#向链表尾部添加新的节点
def append(self,item):
node = Node(item)
if self.isEmpty():#如果链表为空
self.head = node
else:#链表为非空的情况
pre = None #pre要指向cur前面的一个节点
cur = self.head
while cur != None:
pre = cur
cur = cur.next
#循环结束后cur指向了None而pre指向了最后一个节点
pre.next = node
#将新的节点插入到pos表示的位置中
def insertNode(self,item,pos):
node = Node(item)
pre = None
cur = self.head
if pos == 0:
self.addHead(item)
return
for i in range(pos):
pre = cur
cur = cur.next
pre.next = node
node.next = cur
#从链表中删除item表示的节点
def removeNode(self,item):
pre = None
cur = self.head
if self.head.item == item:#删除的是第一个节点
self.head = cur.next
return
while cur != None:
if cur.item == item:
#条件成立则cur就是我们要删除的节点
pre.next = cur.next
break
else:
pre = cur
cur = cur.next
link = Link()
link.addHead(1)
link.addHead(2)
link.addHead(3)
link.addHead(4)
link.addHead(5)
link.append(6)
link.removeNode(5)
link.travel()- 链表如何进行列表弊端的优化?