全局变量__name__:
每个模块中都有的一个全部变量__name__.
__name__的作用是获取当前模块的名称,如果当前模块是单独执行的,则其__name__的值就是__main__;否则,如果是作为模块导入,则其__name__的值就是模块的名字。
包:
Python中的包(Package)的作用与操作系统中文件夹的作用相似,利用包可以将多个关系密切的模块组成在一起,一方面方便进行各脚本文件的管理,另一方面可以有效避免模块命名冲突问题。
定义一个包,就是创建一个文件夹并在该文件夹下创建一个__init__.py文件,文件夹的名字就是包名。
另外,可以根据需要在该文件夹下再创建子文件夹,子文件夹中创建一个__init__.py文件,则又形成了一个子包。
模块可以放在任何一个包或子包中,在导入模块时需要指定所在的包和子包的名字。例如,如果要导入包A中的模块B,则需要使用“import A.B”
提示:__init__.py可以是一个空文件,也可以包含包的初始化代码或者设置__all__列表。
如果要使用sound包的effects子包的echo模块,则可以通过下面方式导入:
import sound.effects.echo
假设在echo模块中有一个echofilter函数,则调用该函数时必须指定完整的名字(包括各层的包名和模块名),即:
sound.effects.echo.echofilter(实参列表)
也可以使用from import方式导入包中的模块,如:from sound.effects import echo
通过这种方式,也可以正确导入sound包的effects子包的echo模块,而且在调用echo模块中的函数时不需要加包名,如:echo.echofilter(实参列表)
使用from import也可以直接导入模块中的标识符,如:from sound.effects.echo import echofilter
此时调用echofilter函数可直接写作:echofilter(实参列表)
猴子补丁:
猴子补丁是指在运行时动态替换已有的代码,而不需要修改原始代码。
猴子补丁主要用于在不修改已有代码情况下修改其功能或增加新功能的支持。
例如,在使用第三方模块时,模块中的某些方法可能无法满足我们的开发需求。此时,我们可以在不修改这些方法代码的情况下,通过猴子补丁用一些自己编写的新方法进行替代,从而实现一些新的功能。
猴子补丁的由来
首先说个我自己的笑话,话说Python算是我接触的稍微深点儿的第一门动态语言,用Python没多久就知道了有个Gevent,学习Gevent没多久就知道有个“猴子补丁”的概念。最开始觉得这么名字挺乐呵,猴子补丁,为啥叫这么个名儿?是因为猴子的动作迅速灵敏,Gevent也有这个特点,所以叫猴子补丁么?
然后这几天在看《松本行弘的程序世界》这本书,里面专门有一章讲了猴子补丁的设计,我就笑了,原来猴子补丁不是我理解的这个意思,更不是Gevent最开始这么做的。所谓的猴子补丁的含义是指在动态语言中,不去改变源码而对功能进行追加和变更。猴子补丁的这个叫法起源于Zope框架,大家在修正Zope的Bug的时候经常在程序后面追加更新部分,这些被称作是“杂牌军补丁(guerilla patch)”,后来guerilla就渐渐的写成了gorllia(猩猩),再后来就写了monkey(猴子),所以猴子补丁的叫法是这么莫名其妙的得来的。
从Gevent学习猴子补丁的设计
猴子补丁这种东西充分利用了动态语言的灵活性,可以对现有的语言Api进行追加,替换,修改Bug,甚至性能优化等等。比如gevent的猴子补丁就可以对ssl、socket、os、time、select、thread、subprocess、sys等模块的功能进行了增强和替换。我们来看下gevent中的猴子补丁模块gevent.monkey的设计和实现,以后如果自己要设计实现猴子补丁,也可以按照这么个模式去做,我最近比较喜欢用ipython来阅读python模块的代码,执行import gevent.monkey之后,只需要输入??gevent.monkey就可以查看源码了。
变量的作用域和局部变量:
变量的作用域是指变量的作用范围,即定义一个变量后,在哪些地方可以使用这个变量。
按照作用域中的不同,python中的变量可分为局部变量和全局变量。
局部变量:在一个函数中定义的变量就是局部变量(包括形参),其作用域中是从定义局部变量的位置至函数结束位置。
全局变量:在所有函数外定义的变量就是全局变量,其在所有函数中都可以使用。
global关键字:在一个函数中使用global关键字,可以声明在该函数中使用的是全局变量、而非局部变量。
在一个函数中要修改全局变量的值,必须使用global关键字声明使用该全局变量。
nonlocal关键字:在python中,函数的定义可以嵌套,即在一个函数的函数体中可以包含另一个函数的定义。通过nonlocal关键字,可以使内层的函数直接使用外层函数中定义的变量。
def outer(): #定义函数outer
x=10 #定义局部变量x并赋为10
def inner(): #在outer函数中定义嵌套函数inner
nonlocal x #通过“nonlocal x”声明在inner函数中使用outer函数中定义的变量x、而不是重新定义一个局部变量x。
x=20 #将x赋为20
print('inner函数中的x值为:',x)
inner() #在outer函数中调用inner函数
print('outer函数中的x值为:',x)
outer() #调用outer函数
out:
inner函数中的x值为: 20
outer函数中的x值为: 20
递归函数:
递归函数是指在一个函数内部通过调用自己来完成一个问题的求解。
当我们在进行问题分解时,发现分解之后待解决的子问题与原问题有着相同的特性和解法,只是在问题规模上与原问题相比有所减小,此时,就可以设计递归函数进行求解。
比如,对于计算n!的问题,可以将其分解为:n! = n*(n-1)!。可见,分解后的子问题(n-1)!与原问题n!的计算方法完全一样,只是规模有所减小。同样,(n-1)!这个子问题又可以进一步分解为(n-1)*(n-2)!,(n-2)!可以进
一步分解为(n-2)*(n-3)!…,直到要计算1!时,直接返回1。
编写递归函数计算n的阶乘:
def fac(n): #定义函数fac
if n==1: #如果要计算1的阶乘,则直接返回1(结束递归调用的条件)
return 1
return n*fac(n-1) #将计算n!分解为n*(n-1)!
当问题规模较大时,递归调用会涉及到很多层的函数调用,一方面会由于栈操作影响程序运行速度,另一方面在Python中有栈的限制、太多层的函数调用会引起栈溢出问题(如将第5行的fac(5)改为fac(1000)则会报错)。
使用非递归方式计算n的阶乘:
def jiecheng(n):
r=1
for x in range(2,n+1):
r=r*x
return r
高阶函数:
高阶函数是指把函数作为参数的一种函数。
def FunAdd(f,x,y): #定义函数FunAdd
return f(x)+f(y) #用传给f的函数先对x和y分别处理后,再求和并返回
函数不仅可以赋给形参,也可以赋给普通变量。赋值后,即可以用变量名替代函数名完成函数调用。
lambda函数:
lambda [参数1[, 参数2, ..., 参数n]]: 表达式
冒号后面的表达式的计算结果即为该lambda函数的返回值。
>>hs=lambda x,y:x*2+y
>>hs(2,3)
out:
7
闭包:
如果内层函数使用了外层函数中定义的局部变量,并且外层函数的返回值是内层函数的引用,就构成了闭包。
定义在外层函数中但由内层函数使用的变量被称为自由变量。
一般情况下,如果一个函数结束,那么该函数中定义的局部变量就都会释放。
然而,闭包是一种特殊情况,外层函数在结束时会发现其定义的局部变量将来会在内层函数中使用,此时外层函数就会把这些自由变量绑定到内层函数。
因此,所谓闭包,实际上就是将内层函数的代码以及自由变量(外层函数定义、但会由内层函数使用)打包在一起。
闭包的主要作用在于可以封存函数执行的上下文环境。例如,通过两次调用out函数形成了两个闭包,这两个闭包具有相互独立的上下文环境(一个闭包中x=5、y=10,另一个闭包中x=50、y=10),且每个闭包可多次调用。
装饰器:
利用装饰器,可以在不修改已有函数的情况下向已有函数中注入代码,使其具备新的功能。
一个装饰器可以为多个函数注入代码,一个函数也可以注入多个装饰器的代码。
利用装饰器可以将日志处理、执行时间计算等较为通用的代码注入到不同的函数中,从而使得代码更加简洁。
@property装饰器
类中的属性可以直接访问和赋值,这为类的使用者提供了方便,但也带来了问题:类的使用者可能会给一个属性赋上超出有效范围的值。
为了解决这个问题,Python提供了@property装饰器,可以将类中属性的访问和赋值操作自动转为方法调用,这样可以在方法中对属性值的取值范围做一些条件限定。
直接使用@property就可以定义一个用于获取属性值的方法(即getter)。
如果要定义一个设置属性值的方法(setter),则需要使用名字“@属性名.setter”的装饰器。
如果一个属性只有用于获取属性值的getter方法,而没有用于设置属性值的setter方法,则该属性是一个只读属性,只允许读取该属性的值、而不能设置该属性的值。
例:通过@property装饰器使得学生成绩的取值范围必须在0~100之间。
面向对象:
面向对象是当前流行的程序设计方法,其以人类习惯的思维方式,用对象来理解和分析问题空间,使开发软件的方法与过程尽可能接近人类认识世界、解决问题的思维方法与过程。
面向对象方法的基本观点是一切系统都是由对象构成的,每个对象都可以接收并处理其他对象发送的消息,它们的相互作用、相互影响,实现了整个系统的运转。
类和对象:
类和对象是面向对象程序设计的两个重要概念。
类和对象的关系即数据类型与变量的关系,根据一个类可以创建多个对象,而每个对象只能是某一个类的对象。
类规定了可以用于存储什么数据,而对象用于实际存储数据,每个对象可存储不同的数据。
提示:与C/C++等语言不同,Python中提供的基本数据类型也是类,如int、float等。
类中包含属性和方法。属性对应一个类可以用来保存哪些数据,而方法对应一个类可以支持哪些操作(即数据处理)。
私有属性,是指在类内可以直接访问、而在类外无法直接访问的属性。
构造方法是Python 类中的内置方法之一,它的方法名为__init__,在创建一个类对象时会自动执行,负责完成新创建对象的初始化工作。
析构方法是类的另一个内置方法,它的方法名为__del__,在销毁一个类对象时会自动执行,负责完成待销毁对象的资源清理工作,如关闭文件等。
__str__方法的返回值必须是 字符串。
通过继承,可以基于已有类创建新的类,新类除了继承已有类的所有属性和方法,还可以根据需要增加新的属性和方法。
通过多态 ,可以使得在执行同一条语句时,能够根据实际使用的对象类型决定调用哪个方法。
如果一个类C1 通过继承已有类C 而创建,则将C1 称作 子类 ,将C 称做基类、父类或超类。
如果一个子类只有一个父类,则将这种继承关系称为 单继承;如果一个子类有两个或更多父类,则将这种继承关系称为多重继承。
方法重写是指子类可以对从父类中继承过来的方法进行重新定义,从而使得子类对象可以表现出与父类对象不同的行为。
内置函数isinstance 用于判断一个对象所属的类是否是指定类或指定类的子类。
内置函数issubclass 用于判断一个类是否是另一个类的子类。
内置函数 type 用于获取一个对象所属的类。
Python 提供了__slots__变量以限制可动态扩展的属性。
Python 提供了@property 装饰器,可以将类中属性的访问和赋值操作自动转为方法调用,这样可以在方法中对属性值的取值范围做一些条件限定。
直接使用@property 可以定义一个用于获取属性值的方法(即getter)。如果需要对属性score 定义一个设置属性值的方法(setter),需要用到的装饰器为@score.setter。
元类 可以看成是创建类时所使用的模板,也可以理解为是用于创建类的类。
单例模式 是指在程序运行时确保某个类最多只有一个实例对象。
在执行__init__前,还会执行类的一个内置的类方法__new__,其作用是创建对象并返回。
类方法是指使用@classmethod 修饰的方法
类方法的第一个参数是类本身(而不是类的实例对象)
类方法既可以通过类名直接调用,也可以通过类的实例对象调用。
类的定义形式多样:
我们既可以直接创建新的类,也可以基于一个或多个已有的类创建新的类;
我们既可以创建一个空的类,然后再动态添加属性和方法,也可以在创建类的同时设置属性和方法。
类的封装性:
类中的属性对应前面所学习的变量,而类中的方法对应前面所学习的函数。通过类,可以把数据和操作封装在一起,从而使得程序结构更加清晰,这也就是所谓的类的封装性。
创建类的实例对象:
提示:每次创建对象时,系统都会在内存中选择一块区域分配给对象,每次选择的内存通常是不一样的。因此,实际运行时会看到一个不同的stu对象地址。
定义类时指定类属性:
类中普通方法定义及调用:
类中的方法实际上就是执行某种数据处理功能的函数。
与普通函数一样,类中的方法在定义时也需要使用def关键字。
类中的方法分为两类:普通方法和内置方法。普通方法需要通过类的实例对象根据方法名调用;内置方法是在特定情况下由系统自动执行。
类的私有属性:
私有属性,是指在类内可以直接访问、而在类外无法直接访问的属性。
Python中规定,在定义类时,如果一个类属性名是以__(两个下划线)开头,则该类属性为私有属性。