1.1 使用函数
在Python中,函数必须先声明,然后才能调用它,使用函数时,只要按照函数定义的形式,向函数传递必需的参数,就可以调用函数完成相应的功能或者获得函数返回的处理结果。
如果函数有返回值,那么需要在函数中使用return语句返回计算结果,声明函数的一般形式如下。
1.1.1 声明函数并调用
def <函数名 > (参数列表):
<函数语句>
return <返回值>
其中参数列表和返回值不是必须的,return后也可以不跟返回值,甚至连return也没有。对于return后没有返回值的和没有return语句的函数都会返回None值。
有些函数可能既不需要传递参数,也没有返回值。
def tpl_sum( T ):
result = 0
for i in T:
result += i
return result
1.2深入函数
1.2.1默认值参数
声明一个参数具有默认值的函数形式如下。
def <函数名> (参数=默认值):
<语句>
声明了一个带默认值参数的函数,代码如下:
def hello (name='Python'):
print ('你好,%s!' % name)
print ('无参数调用时的输出:')
hello()
print ('有参数("Jonson")调用时的输出:')
hello('Jonson')
1.2.2参数传递
调用函数提供参数时,按顺序传递的参数要位于关键字参数之前,而且不能有重复的情况。
1.2.3可变数量的参数传递
在自定义函数时,如果参数名前加上一个星号“*”,则表示该参数就是一个可变长参数。在调用该函数时,如果依次序将所有的其他变量都赋予值之后,剩下的参数将会收集在一个元组中,元组的名称就是前面带星号的参数名。
def change_para_num (*tp1):
print (type (tp1)) #输出tp1变量的类型
print (tp1)
change_para_num (1)
change_para_num (1,2,3)
当自定义函数时,参数中含有前面所介绍的三种类型的参数,则一般来说带星号的参数应放在最后。当带星号的参数放在最前面时,仍然可以正常工作,但调用时后面的参数必须以关键字参数方式提供,否则因其后的位置参数无法获取值而引发错误。
def change_para_num(*tpl,a,b=0,,**dict):
print('tp1:',tp1)
print("dict",dict)
print('a:',a)
print ('b:',b)
change_para_num (1,2,3,a=5,c=1,d=2,e=3) #c d e 会以字典的形式储存
change_para_num(1,2,3) #该调用会出错,a变量没有默认值,也不能获取值
如果要收集不定数量的关键字参数
可以在自定义函数时的参数前加两颗星即**valuename,可以以字典的方式被收集到变量valuename之中。
def cube (name, **nature):
all_nature = {'x':1,
'y':1,
'z':1,
'color':'white',
'weight':1}
all_nature.update (nature)
print (name,"立方体的属性:")
print ('体积:',all_nature ['x']*all_nature ['y']*all_nature ['z'])
print ('颜色:',all_nature ['color'])
print ('重量:',all_nature ['weight'])
cube ('first') #只给出必要参数的调用
cube ('second',y=3, color='red') #提供部分可选参数
cube ('third', z=2, color='green', weight=10) #提供部分可选参数
1.2.4拆解序列的函数调用
前面使用函数调用时提供的参数都是位置参数
和关键字参数
,实际上调用函数时还可以把元组和字典进行拆解调用。
- 拆解元组提供位置参数;
- 拆解字典提供关键字参数。
调用时使用拆解元组的方法是在调用时提供的参数前加一个*号;要拆解字典必须在提供的调用参数前加两个*号。
def mysum (a,b):
return a+b
print ('拆解元组调用:')
print (mysum(* (3,4))) #调用时拆解元组作为位置参数
print ('拆解字典调用:')
print (mysum(**{'a':3, 'b':4}) #调用时拆解字典作为关键字参数
1.2.5函数调用时参数的传递方法
Python中的元素有可变和不可变之分,如整数、浮点数、字符串、元组
等都属于不可变的;而列表和字典
都属于可变的。
列表和字典的可变也是很好理解的,即它们可以增减元素、修改元素的值。那么整数、浮点数、字符串等为什么是不可变的呢?
“=”号的作用是将对象引用与内存中某对象进行绑定,既然整数是不可变的,那么怎么改变一个指向整数的变量的值的呢?答案是直接在内存中创建一个新的整数值,然后将变量引用与其绑定,这虽然本质上的其他高级语言不同,但在使用上是看不出什么差别的,但若将其提供给函数作为参数,则效果不同。
在函数调用时,若提供的是不可变参数,那么在函数内部对其修改时,在函数外部其值是不变的;若提供是可变参数,则在函数内部对它修改时,在函数外部其值也会改变的。
def change (aint, alst): #定义函数
aint = 0 #修改 aint 值
alst[0]=0 #修改alst第1个值为0
alst.append (4) #在 alst 中添加一个元素4
print ('函数中aint:', aint) #输出函数中的aint的值
print ('函数中alst:', alst) #输出函数中的alst的值
aint = 3
alst = [1,2,3]
print ('调用前aint:', aint)
print ('调用前 alst:', alst)
change(aint, alst)
print ('调用后aint:', aint) #调用后值和调用前值相同
print ('调用后alst:', alst) #调用后值和调用前值不同
def myfun (lst= []): #定义有一个默认值为空列表的参数
lst.append('abc')
print (lst)
#此后三次调用自定义函数
myfun()
myfun()
myfun()
#每次调用函数按默认值的理解,应该每次传入空列表,列表中只有一个元素'abc'
#在该函数的域上lst是已经存在的,在一个线程内多次执行会保留上一次执行的值。
def myfun (lst=None): #定义有一个默认值为空列表的参数
lst = [] if lst is None else lst
lst.append('abc')
print (lst)
myfun()
myfun()
myfun()
#增加一条推导语句,将lst置空,就可以达到想要的效果。
1.3变量的作用域
- 内置作用域:Python预先定义的;
- 全局作用域:所编写的整个程序;
- 局部作用域:某个函数内部范围。
每次执行函数,都会创建一个新的命名空间,这个新的命名空间就是局部作用域,同一函数不同时间运行,其作用域是独立的,不同的函数也可以具有相同的参数名,其作用域也是独立的。
在函数内已经声明的变量名,在函数以外依然可以使用。并且在程序运行的过程中,其值并不相互影响。
def myfun():
a = 0 #函数内声明并初始化变量a为整数,局部作用域
a += 3 #修改a的值
print ('函数内a:', a) #输出函数内a的值
a = 'external' #全局作用域内a声明并初始化
print ('全局作用域a:', a)
myfun() #调用函数myfun()
print('全局作用域a:', a)
#总结:函数内部声明的a和外部声明的a在各自的域上互不干扰!
#那么如何在函数内使用全局作用域的变量呢?
def myfun():
global a #增加此语句
a = 0
a += 3
print ('函数内a:', a)
a = 'external'
print ('全局作用域a:',a)
myfun()
print ('全局作用域a:', a)
在局部作用域内可以引用全局作用域内的变量,但不可以修改它。
比如以下代码是没有错误的:
a = 3 #定义全局变量
def myprint(): #声明函数myprint()
print (a) #引用全局变量
运行函数myprint()时,会输出全局变量a的值3。但是若将其改为则会引发错误:
a = 3 #定义全局变量
def myprint(): #声明函数myprint()
print (a) #引用全局变量
a = 5
以上代码引发的错误是局部变量在分配前不能引用,原因是与Python中的变量查找有关,在此外代码中函数内声明了a变量并初始化,所以a被判为局部变量,但却之前在print(a)中引用了它。
Python中的变量查找:
1.4使用匿名函数(lambda)
语法形式如下:
lambda params:expr
其中params相当于声明函数时的参数列表中用逗号分隔的参数,expr是函数要返回值的表达式,而表达式中不能包含其他语句,也可以返回元组(要用括号),还允许在表达式中调用其他函数。
lambda的示例代码:
import math
s = lambda x1, y1, x2, y2:math. sqrt( (x1-x2) **2+(y1-y2) **2)
s(1,1,0,0)
#结果:1.4142135623730951
代码的第二行定义了一个求两坐标点距离的匿名函数,并用s引用,之后调用它来求(1,1)与(0,0)坐标点的距离,结果为1.414。
这里的params是参数列表。它的结构与Python中函数(function)的参数列表是一样的。具体来说,argument_list可以有非常多的形式。例如:
a, b
a=1, b=2
*args
**kwargs
a, b=1, *args
这里的expr是一个关于参数的表达式。表达式中出现的参数需要在argument_list中有定义,并且表达式只能是单行的。以下都是合法的表达式:
1
None
a + b
sum(a)
1 if a >10 else 0
下面是一些lambda函数示例:
lambda x, y: x*y;函数输入是x和y,输出是它们的积x*y
lambda:None;函数没有输入参数,输出是None
lambda *args: sum(args); 输入是任意个数的参数,输出是它们的和(隐性要求是输入参数必须能够进行加法运算)
lambda **kwargs: 1;输入是任意键值对参数,输出是1
- 简单匿名函数 写起来快速而简单,省代码;
- 不复用的函数 在有些时候需要一个抽象简单的功能,又不想单独定义一个函数;
- 为了代码清晰 有些地方使用它,代码更清晰易懂。
- 比如在某个函数中需要一个重复使用的表达式,就可以使用lambda来定义一个匿名函数,多次调用时,就可以少写代码,但条理清晰。
1.5Python常用内建函数
没有导入任何模块或包时Python运行时提供的函数称为内建函数
dir(obj) | 列出对象的相关信息 |
---|---|
help(obj) | 显示对象的帮助信息 |
bin(aint) | 十进制数转换为二进制数的字符串形式 |
hex(aint) | 十进制数转换为十六进制数的字符串形式 |
oct(aint) | 十进制数转换为八进制数的字符串形式 |
callable(obj) | 测试对象是否可调用(函数) |
chr(aint) | ascii码转为字符 |
ord(char) | 将字符转为ascii码 |
filter(seq) | 简单的理解为过滤器,需要两个参数,function,和一个序列(字符串、列表、元组都是序列),过滤器会依次将序列的值传入function中,如果返回True的话,将其重新生成一个列表返回。 |
map(seq) | 和filter()类似,也是将序列放入函数进行运算,但是,不论运算结果为什么,map()都将忠实反馈,这是map()和filter()的主要区别。请注意,filter()和map()中的function都必要有一个返回值。 |
isinstance(obj,typestr) | 测试对象是否为某类型 |
print(list(filter(lambda x:True if x % 3 == 0 else False, range(100)))) #返回100以内3的倍数
print(list(map(lambda x:True if x % 3 == 0 else False, range(100))))#返回是一堆true或者false
print(list(filter (lambda x:x % 2, alst)))#当x为偶数时生成一个新列表将其加入
print(list (map (lambda x:2*x,alst)))#list(map(lambda x:2*x,alst))中的lambda x:2*x,就是用lambda定义的一个匿名函数,并通过map()函数,将其应用到alst列表中的每个元素。
1.6类的属性和方法
class <类名>(父类名):
pass
class MyClass():
"pass"
myclass=MyClass()
help(MyClass) 输出MyClass的帮助信息
print(myclass.__doc__) 输出类的描述信息为pass
1.6.1类的方法
类中的方法定义和调用与函数定义和调用的方式基本相同,其区别有:
- 方法的第一个参数必须是self,而且不能省略
- 方法的调用需要实例化类,并以实例名.方法名(参数列表)形式调用;
- 整体进行一个单位的缩进,表示其属于类体中的内容。
class Demolnit:
def __init__(self,x,y=0): #定义构造方法,具有两个初始化
self.x = x
self.y = y
def mycacl (self): #定义应用初始化数据的方法
return self.x + self.y
def info (self): #定义一个类方法info()
print ('我定义的类!')
def mycacl_2 (self,x,y): #定义一个类方法mycacl()
return x + y
dia = Demolnit (3) #用一个参数实例化类
print ('调用mycacl方法的结果1:')
print (dia.mycacl())
dib = Demolnit (3, 7) #用二个参数实例化类
print ('调用mycacl方法的结果2:')
print (dib.mycacl())
def coord_chng(x,y): #定义一个全局函数,模拟坐标值变换
return (abs (x),abs (y)) #将x,y值求绝对值后返回
class Ant: #定义一个类Ant
def __init__(self,x=0,y=0): #定义一个构造方法
self.x = x
self.y = y
self.disp_point() #构造函数中调用类中的方法disp_point()
def move (self,x, y): #定义一个方法move()
x,y = coord_chng(x,y) #调用全局函数,坐标变换
self.edit_point (x, y) #调用类中的方法edit_point()
self.disp_point() #调用类中的方法disp_point()
def edit_point (self, x,y): #定义一个方法
self.x += x
self.y += y
def disp_point (self): #定义一个方法
print ("当前位置:(%d,%d)" % (self .x, self.y))
ant_a = Ant() #实例化Ant()类
ant_a.move(2,4) #调用ant_a实例的方法move()
ant_a.move (-9, 6) #调用ant_a实例的方法move()
1.6.2类的属性
类的属性有两类:
-
实例属性
; -
类属性
。
实例属性
即同一个类的不同实例,其值是不相关联的,也不会互相影响的,定义时使用“self.属性名
”,调用时也使用它;类属性
则是同一个类的所有实例所共有的,直接在类体中独立定义,引用时要使用“类名.类变量名
”形式来引用,只要是某个实例对其进行修改,就会影响其他的所有这个类的实例。
class Demo_Property: #定义类
class_name = "Demo_Property" #类属性
def __init__(self,x=0):
self.x = x #实例属性
def class_info (self): #输出信息的方法
print ('类变量值:', Demo_Property.class_name)
print ('实例变量值:', self.x)
def chng (self,x): #修改实例属性的方法
self.x = x #注意实例属性的引用方式
def chng_cn (self, name): #修改类属性的方法
Demo_Property.class_name = name #注意类属性引用方式
dpa = Demo_Property() #实例化类
dpb = Demo_Property() #实例化类
print ('初始化两个实例')
dpa.class_info()
dpb.class_info()
print ('修改实例变量')
print ('修改dpa实例变量')
dpa.chng(3)
dpa.class_info()
dpb.class_info()
print ('修改dpb实例变量')
dpb.chng(10)
dpa.class_info()
dpb.class_info()
#这里得到实例变量是属于每个对象自己的。
print ('修改类变量')
print ('修改dpa类变量')
dpa.chng_cn('dpa')
dpa.class_info()
dpb.class_info()
print ('修改dpb实例变量')
dpb.chng_cn('dpb')
dpa.class_info()
dpb.class_info()
#这里得到类变量是属于每个对象共有的,一旦修改每个对象的类变量都会相应的改变。
综上:对于实例属性来说,两个实例之间互不联系,它们各自可以被修改为不同的值;对于类属性来说,无论哪个实例修改了它,会导致所有实例的类属性的值发生变化。
1.6.3 类成员方法与静态方法
类的属性有类属性和实例属性之分,类的方法也有不同的种类,主要有:
- 实例方法;
- 类方法;
- 静态方法。
前文中定义的所有类中的方法都是实例方法,其隐含调用参数是类的实例。类方法隐含调用参数则是类,静态方法没有隐含调用参数。类方法和静态方法的定义方式都与实例方法不同,它们的调用方式也不同。
静态方法定义时应使用装饰器@staticmethod
进行修饰,是没有默认参数的。类方法定义时应使用装饰器@classmethod
进行修饰,必须有默认参数“cls”。它们的调用方式可以直接由类名进行调用,调用前也可以不实例化类,当然也可以用该类的任一个实例来进行调用。
class DemoMthd: #定义一个类
@staticmethod #静态方法的装饰器
def static_mthd(): #静态类定义
print ('调用了静态方法!')
@classmethod #类方法的装饰器
def class_mthd(cls): #类方法定义,带默认参数cls
print ('调用了类方法!')
DemoMthd.static_mthd() #未实例化类,通过类名调用静态方法
DemoMthd.class_mthd() #未实例化类,通过类名调用类方法
dm = DemoMthd() #实例化类
dm.static_mthd() #通过类实例调用静态方法
dm.class_mthd() #通过类实例雅尔塔类方法
1.7类的继承
类是可以继承的,并且也给出了继承类的代码格式。子类继承了父类之后,就具有了父类的属性与方法,但不能继承父类的私有属性和私有方法(属性名或方法名前缀为两个下画线的__
),子类中还可以重载来修改父类的方法,以实现与父类不同的行为表现或能力。
1.7.1 类的继承
演示了方法的重写和添加
class Ant: #定义类Ant
def __init__(self,x=0,y=0,color='black'):#定义构造方法
self.x = x
self.y = y
self.color =color
def __crawl (self,x,y): #定义方法(模拟爬行)
self.x = x
self.y = y
print ('爬行...')
self.info()
def info(self):
print ('当前位置:(%d,%d)' % (self.x, self.y))
def attack (self): #定义方法(模拟攻击)
print ("用嘴咬!")
class FlyAnt(Ant): #定义FlyAnt类,继承Ant类
def attack (self): #修改行为(攻击方法不同)
print ("用尾针!")
def fly(self,x,y): #定义方法(模拟飞行)
print ('飞行...')
self.x = x
self.y = y
self.info()
flyant = FlyAnt (color='red') #实例化类
flyant.crawl(3,5) #__crawl此时会报错无法继承父类实例方法
#flyant.crawl(3,5) #调用方法(模拟爬行)
flyant.fly(10,14) #调用访求(模拟飞行)
flyant.attack()
#调用方法(模拟攻击)
1.7.2 多重继承
在面向对象编程的语言中,有的允许多重继承,即一个类可以继承多个类;有的只允许单一继承,即一个类只能继承一个类,而Python中则允许多重继承。
多重继承的方式是在类定义时的继承父类的括号中,以“,”分隔开要多重继承的父类即可。而多重继承时,继承顺序也是一个很重要的要素,如果继承的多个父类中有相同的方法名,但在类中使用时未指定父类名,则Python解释器将从左至右搜索,即调用先继承的类中的同名方法。
1.7.3 方法的重载
当子类继承父类时,子类如果要想修改父类的行为,则应使用方法重载来实现,方法重载的基本方法是在子类中定义一个和所继承的父类中需要重载方法同名的一个方法即可。
多重继承中,两个父类都具有同名方法info(),但在子类中也定义了一个info()方法,即info()方法被重载了。当子类实例调用info()方法时,就会直接调用该实例中定义的info()方法,而不会去调用任何一个父类的info()方法。
综合来讲一个同名方法先找子类,然后父类由先继承到后继承。
装饰器重载:
import functools
import locale
from urllib import request
# 函数重载
@functools.singledispatch
def fun(arg, verbose=False):
if verbose:
print("Let me just say,", end=" ")
print(arg)
@fun.register(int)
def _(arg, verbose=False):
if verbose:
print("Strength in numbers, eh?", end=" ")
print(arg)
@fun.register(list)
def _(arg, verbose=False):
if verbose:
print("Enumerate this:")
for i, elem in enumerate(arg):
print(i, elem)
print('函数重载')
fun("test.", verbose=True)
fun(42, verbose=True)
fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
fun({'a':'txt','b':'dat'}, verbose=True)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。