函数与类
函数
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
定义一个函数
你可以定义一个由自己想要功能的函数,以下是简单的规则:
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。函数内容以冒号起始,并且缩进。return[表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
- 语法
Python 定义函数使用 def 关键字,一般格式如下:
def 函数名(参数列表):
函数体
默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
实例
#让我们使用函数来输出"Hello World!":
>>>def hello() :
print("Hello World!")
>>> hello()
Hello World!
>>>
更复杂点的应用,函数中带上参数变量:
实例(Python 3.0+)
#!/usr/bin/python3
## 计算面积函数
def area(width, height):
return width * height
def print_welcome(name):
print("Welcome", name)
print_welcome("Runoob")
w = 4
h = 5
print("width =", w, " height =", h, " area =", area(w, h))
以上实例输出结果:
Welcome Runoob
width = 4 height = 5 area = 20
函数调用
定义一个函数:给了函数一个名称,指定了函数里包含的参数,和代码块结构。
这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从 Python 命令提示符执行。
如下实例调用了 printme() 函数:
## 定义函数
def printme( str ):
## 打印任何传入的字符串
print (str)
return
## 调用函数
printme("我要调用用户自定义函数!")
printme("再次调用同一函数")
以上实例输出结果:
我要调用用户自定义函数!
再次调用同一函数
参数传递
在 python 中,类型属于对象,变量是没有类型的:
a=[1,2,3]
a="Runoob"
以上代码中,[1,2,3] 是 List 类型,“Runoob” 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。
可更改(mutable)与不可更改(immutable)对象
在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
python 函数的参数传递:
不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
-
python 传不可变对象实例
实例(Python 3.0+) #!/usr/bin/python3
def ChangeInt( a ): a = 10
b = 2 ChangeInt(b) print( b ) ## 结果是 2
实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它。
- 传可变对象实例
可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了。例如:
实例(Python 3.0+) #!/usr/bin/python3
## 可写函数说明
def changeme( mylist ):
"修改传入的列表"
mylist.append([1,2,3,4])
print ("函数内取值: ", mylist)
return
## 调用changeme函数
mylist = [10,20,30]
changeme( mylist )
print ("函数外取值: ", mylist)
传入函数的和在末尾添加新内容的对象用的是同一个引用。故输出结果如下:
函数内取值: [10, 20, 30, [1, 2, 3, 4]]
函数外取值: [10, 20, 30, [1, 2, 3, 4]]
参数
以下是调用函数时可使用的正式参数类型:
必需参数
关键字参数
默认参数
不定长参数
必需参数
必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
调用 printme() 函数,你必须传入一个参数,不然会出现语法错误:
实例(Python 3.0+)
#!/usr/bin/python3
#可写函数说明
def printme( str ):
"打印任何传入的字符串"
print (str)
return
## 调用 printme 函数,不加参数会报错
printme()
以上实例输出结果:
Traceback (most recent call last):
File "test.py", line 10, in <module>
printme()
TypeError: printme() missing 1 required positional argument: 'str'
关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
以下实例在函数 printme() 调用时使用参数名:
实例(Python 3.0+)
#!/usr/bin/python3
#可写函数说明
def printme( str ):
"打印任何传入的字符串"
print (str)
return
#调用printme函数
printme( str = "菜鸟教程")
以上实例输出结果:
菜鸟教程
以下实例中演示了函数参数的使用不需要使用指定顺序:
实例(Python 3.0+)
#!/usr/bin/python3
#可写函数说明
def printinfo( name, age ):
"打印任何传入的字符串"
print ("名字: ", name)
print ("年龄: ", age)
return
#调用printinfo函数
printinfo( age=50, name="runoob" )
以上实例输出结果:
名字: runoob
年龄: 50
默认参数
调用函数时,如果没有传递参数,则会使用默认参数。以下实例中如果没有传入 age 参数,则使用默认值:
实例(Python 3.0+)
#!/usr/bin/python3
#可写函数说明
def printinfo( name, age = 35 ):
"打印任何传入的字符串"
print ("名字: ", name)
print ("年龄: ", age)
return
#调用printinfo函数
printinfo( age=50, name="runoob" )
print ("------------------------")
printinfo( name="runoob" )
以上实例输出结果:
名字: runoob
年龄: 50
------------------------
名字: runoob
年龄: 35
不定长参数
你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述 2 种参数不同,声明时不会命名。基本语法如下:
def functionname([formal_args,] *var_args_tuple ):
"函数_文档字符串"
function_suite
return [expression]
加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
实例(Python 3.0+)
#!/usr/bin/python3
## 可写函数说明
def printinfo( arg1, *vartuple ):
"打印任何传入的参数"
print ("输出: ")
print (arg1)
print (vartuple)
## 调用printinfo 函数
printinfo( 70, 60, 50 )
以上实例输出结果:
输出:
70
(60, 50)
如果在函数调用时没有指定参数,它就是一个空元组。我们也可以不向函数传递未命名的变量。如下实例:
实例(Python 3.0+)
#!/usr/bin/python3
## 可写函数说明
def printinfo( arg1, *vartuple ):
"打印任何传入的参数"
print ("输出: ")
print (arg1)
for var in vartuple:
print (var)
return
## 调用printinfo 函数
printinfo( 10 )
printinfo( 70, 60, 50 )
以上实例输出结果:
输出:
10
输出:
70
60
50
还有一种就是参数带两个星号 **基本语法如下:
def functionname([formal_args,] **var_args_dict ):
"函数_文档字符串"
function_suite
return [expression]
加了两个星号 ** 的参数会以字典的形式导入。
实例(Python 3.0+)
#!/usr/bin/python3
## 可写函数说明
def printinfo( arg1, **vardict ):
"打印任何传入的参数"
print ("输出: ")
print (arg1)
print (vardict)
## 调用printinfo 函数
printinfo(1, a=2,b=3)
以上实例输出结果:
输出:
1
{'a': 2, 'b': 3}
声明函数时,参数中星号 * 可以单独出现,例如:
def f(a,b,*,c):
return a+b+c
如果单独出现星号 * 后的参数必须用关键字传入。
>>> def f(a,b,*,c):
... return a+b+c
...
>>> f(1,2,3) ## 报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 3 were given
>>> f(1,2,c=3) ## 正常
6
>>>
匿名函数
python 使用 lambda 来创建匿名函数。
所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。
- lambda 只是一个表达式,函数体比 def 简单很多。
- lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
语法
lambda 函数的语法只包含一个语句,如下:
lambda [arg1 [,arg2,…..argn]]:expression
如下实例:
实例(Python 3.0+)
#!/usr/bin/python3
## 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2
## 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 ))
print ("相加后的值为 : ", sum( 20, 20 ))
以上实例输出结果:
相加后的值为 : 30
相加后的值为 : 40
return语句
return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。之前的例子都没有示范如何返回数值,以下实例演示了 return 语句的用法:
实例(Python 3.0+)
#!/usr/bin/python3
## 可写函数说明
def sum( arg1, arg2 ):
## 返回2个参数的和."
total = arg1 + arg2
print ("函数内 : ", total)
return total
## 调用sum函数
total = sum( 10, 20 )
print ("函数外 : ", total)
以上实例输出结果:
函数内 : 30
函数外 : 30
强制位置参数
Python3.8 新增了一个函数形参语法 / 用来指明函数形参必须使用指定位置参数,不能使用关键字参数的形式。
在以下的例子中,形参 a 和 b 必须使用指定位置参数,c 或 d 可以是位置形参或关键字形参,而 e 或 f 要求为关键字形参:
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
以下使用方法是正确的:
f(10, 20, 30, d=40, e=50, f=60)
以下使用方法会发生错误:
f(10, b=20, c=30, d=40, e=50, f=60) ## b 不能使用关键字参数的形式
f(10, 20, 30, 40, 50, f=60) ## e 必须使用关键字参数的形式
补充
默认参数必须放在最后面,否则会报:
SyntaxError: non-default argument follows default argument
## 可写函数说明
def printinfo( age=35,name): ## 默认参数不在最后,会报错
"打印任何传入的字符串"
print("名字: ", name)
print("年龄: ", age)
return
def(**kwargs) 把N个关键字参数转化为字典:
>>> def func(country,province,**kwargs):
... print(country,province,kwargs)
...
>>> func("China","Sichuan",city = "Chengdu", section = "JingJiang")
China Sichuan {'city': 'Chengdu', 'section': 'JingJiang'}
>>>
lambda 匿名函数也是可以使用"关键字参数"进行参数传递
>>> g= lambda x,y : x**2+y**2
>>> g(2,3)
13
>>> g(y=3,x=2)
13
同样地,lambda 匿名函数也可以设定默认值
>>> g= lambda x=0,y=0 : x2+y2
>>> g(2,3)
13
>>> g(2)
4
>>> g(y=3)
9
注意:如果只打算给其中一部分参数设定默认值,那么应当将其放在靠后的位置(和定义函数时一样,避免歧义),否则会报错。
可变与不可变
关于可更改与不可更改类型, 以及其它语言的值类型与引用类型的介绍,一直一来感觉都不太严谨, 说法是否正确有待验证。
简单的说就是,不可更改类型传到函数里重新赋值后,两次输出值不一样,而可更改类型传到函数里对对象的"属性" 重新赋值后输出值一样。
这里照搬一下例子:
## 可写函数说明
def changeme( mylist ):
"修改传入的列表"
mylist.append([1,2,3,4])
print ("函数内取值: ", mylist)
return
调用changeme函数
mylist = [10,20,30]
changeme( mylist )
print ("函数外取值: ", mylist)
请注意:上面特意用了引号标准的部分,对可变类型或者引用的操作修改的是传过来的对象的属性。
可以这么理解(例子有点随意):我在画画,小明来了说他也要画,我让他和我一起画,他如果和我在同一个画板上画,那么我们两的画就会同时改变。 而如果他说不,我要另外用一块画板,然后重新拿了块画板画起来了,那么我们两的画自然就不一样了。
同理可更改类型 的属性进行操作,这只是对引用的内存块里面的值进行操作,引用并没变,自然所有引用它的对象的值都变了。而对不可更改的对象进行操作,因为它引用的内存块只是对应一个固定的值,不能进行修改,要重新复制实际上就是更新引用。
如果我们运行下面的例子,对可更改类型的引用进行修改,结果就不一样了。
## 可写函数说明
def changeme( mylist ):
"修改传入的列表"
mylist = [1,2,3,4]
print ("函数内取值: ", mylist)
return
调用changeme函数
mylist = [10,20,30]
changeme( mylist )
print ("函数外取值: ", mylist)
结果
函数内取值: [1, 2, 3, 4]
函数外取值: [10, 20, 30]
变量作用域
对于变量作用域,变量的访问以 L(Local) –> E(Enclosing) –> G(Global) –>B(Built-in) 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
观察以下几个例子,均从内部函数输出变量 x:
-
局部作用域
x = int(3.3)
x = 0 def outer(): x = 1 def inner(): x = 2 print(x) inner()
outer()
执行结果为 2,因为此时直接在函数 inner 内部找到了变量 x。
2.闭包函数外的函数中
x = int(3.3)
x = 0
def outer():
x = 1
def inner():
i = 2
print(x)
inner()
outer()
执行结果为 1,因为在内部函数 inner 中找不到变量 x,继续去局部外的局部——函数 outer 中找,这时找到了,输出 1。
3.全局作用域
x = int(3.3)
x = 0
def outer():
o = 1
def inner():
i = 2
print(x)
inner()
outer()
执行结果为 0,在局部(inner函数)、局部的局部(outer函数)都没找到变量 x,于是访问全局变量,此时找到了并输出。
-
内建作用域
x = int(3.3) g = 0 def outer(): o = 1 def inner(): i = 2 print(x) inner()
outer()
执行结果为 3,在局部(inner函数)、局部的局部(outer函数)以及全局变量中都没有找到变量x,于是访问内建变量,此时找到了并输出。
函数内可以访问全局变量,但不能更新(修改)其值!
例 :
a = 10
def sum ( n ) :
n += a
print ('a = ', a, end = ' , ' )
print ( 'n = ', n )
sum(3)
输出 :
a = 10 , n = 13
如果引用了还没更新的值则会报错 :
a = 10
def sum ( n ) :
n += a
a = 11
print (‘a = ‘, a, end = ’ , ’ )
print ( ’n = ‘, n )
sum(3)
输出 :
…
UnboundLocalError: local variable ‘a’ referenced before assignment
可以加上 global 引用以更新变量值 :
a = 10
def sum ( n ) :
global a
n += a
a = 11
print (‘a = ‘, a, end = ’ , ’ )
print ( ’n = ‘, n )
sum ( 3 )
print ( ‘外 a = ‘, a )
输出:
a = 11 , n = 13 外 a = 11
函数也可以以一个函数为其参数:
def hello () :
print ("Hello, world!")
def execute(f):
"执行一个没有参数的函数"
f()
execute(hello)
输出:
Hello, world!
FVortex
FVortex
函数添加文档
可以通过 函数名.doc 的方式来显示函数的说明文档,感觉这个如果在阅读比较大的程序时应该会有用,同时也在提示自己在写函数时注意添加文档说明。
def add(a,b):
"这是 add 函数文档"
return a+b
print (add.doc)
输出结果为:
这是 add 函数文档
函数返回值的注意事项
不同于 C 语言,Python 函数可以返回多个值,多个值以元组的方式返回:
def fun(a,b):
"返回多个值,结果以元组形式表示"
return a,b,a+b
print(fun(1,2))
输出结果为:
(1, 2, 3)
Mr.Wu
Mr.Wu
函数的装饰器
在不改变当前函数的情况下, 给其增加新的功能:
def log(pr):#将被装饰函数传入
def wrapper():
print("**********")
return pr()#执行被装饰的函数
return wrapper#将装饰完之后的函数返回(返回的是函数名)
@log
def pr():
print("我是小小洋")
pr()
回调函数和返回函数的实例就是装饰器。
更多内容可参考:Python 函数装饰器
全局与局部变量修改问题
1.内部函数,不修改全局变量可以访问全局变量
a = 10
def test():
b = a + 2 #仅仅访问全局变量 a
print(b)
test()
输出结果为:
12
2.内部函数,修改同名全局变量,则python会认为它是一个局部变量(同教程最后一个例 子)
#!/usr/bin/python3
a = 10
def test():
a = a + 1 #修改同名的全局变量,则认为是一个局部变量
print(a)
test()
3.在内部函数修改同名全局变量之前调用变量名称(如print sum),则引发Unbound-LocalError
在这里补充一点关于 global 和 nonlocal 的知识:
nonlocal 只能修改外层函数的变量而不能修改外层函数所引用的全局变量,给一个例子如下:
x = 0
def outer():
global x
x = 1
def inner():
nonlocal x
x = 2
print(x)
inner()
outer()
print(x)
结果会报错:
line 6
nonlocal x
^
SyntaxError: no binding for nonlocal ‘x’ found
richael
richael
global 关键字会跳过中间层直接将嵌套作用域内的局部变量变为全局变量:
测试代码如下:
num = 20
def outer():
num = 10
def inner():
global num
print (num)
num = 100
print (num)
inner()
print(num)
outer()
print (num) 结果如下:
20
100
10
100
init()
Python 定义一个 class 可以编写一个它的构造函数 init() 其中形参 self 在实例化时相当于 myname:
class demo:
name = ""
def _init_(self):
self.ex()
self.start()
def inputName(self):
global name
name = input("輸入您的姓名:")
def getFirstName(self):
if len(name) <= 0:
x = "別鬧!請輸入姓名!"
return x
else:
x = name[0]
return x
def getLastName(self):
if len(name) <= 1:
y = "別鬧!長度不夠!"
return y
else:
y = name[1:]
return y
myname = demo()
myname.inputName()
print(myname.getFirstName())
print(myname.getLastName())
函数的参数分为形参和实参。
- 什么是形参
对于函数来说,形式参数简称形参,是指在定义函数时,定义的一个变量名。
下面的代码中,x、y、z 就是形参。
#定义函数
def foo(x, y, z):
print("x=", x)
print("y=", y)
print("z=", z)
#调用函数
foo(1,3,5) #此处的1,3,5是实参
输出结果:
x= 1
y= 3
z= 5
形参的作用:是用来接收调用函数时输入的值。
- 什么是实参
对于函数来说,实际参数简称实参。
是指在调用函数时传入的实际的数据,这会被绑定到函数的形参上。
函数调用时,将值绑定到变量名上,函数调用结束,解除绑定,并且实参将不再存在于程序中。
foo(5,2,0) 上面的 5、 2 和 0 都是实参。
在编写函数的过程中,可以显式指定函数的参数类型及返回值类型:
#!/usr/bin/env python3
## -*- coding: UTF-8 -*-
def function_demo(param_A: int, param_B: float, param_C: list, param_D: tuple) -> dict:
pass
这种 “将数据类型写死在代码中” 的行为在集成开发环境/代码编辑器时尤为方便,通过显式地指定函数的参数类型和返回值,能够让智能补全组件提前获知标识符的数据类型,提供有利的辅助开发功能。
1.向函数传递的函数本身可以是有参数的:
def demo(*p): i=0 for var in p: var(i) i+=1 def d1(i): print(“这里是第一个子函数,输入的数是”,i) def d2(i): print(“这里是第二个子函数,输入的数是”,i) def d3(i): print(“这里是第三个子函数,输入的数是”,i) def d4(i): print(“这里是第四个子函数,输入的数是”,i) demo(d1,d2,d3,d4) 上面的代码执行起来没问题。
2.就算你的函数是有参数的,将这个函数传递给另一个函数的时候也不能加参数,还是上面的例子:
demo(d1(7),d2,d3,d4) 这样就会报错,因为此时 d1(7) 就是 d1() 的返回值,是不可以在方法内部传递参数并且 调用。
Python3 面向对象
Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。
如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。
接下来我们先来简单的了解下面向对象的一些基本特征。
面向对象技术简介
类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
方法:类中定义的函数。
类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
局部变量:定义在方法中的变量,只作用于当前实例的类。
实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)“关系(例图,Dog是一个Animal)。
实例化:创建一个类的实例,类的具体对象。
对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。
Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。
对象可以包含任意数量和类型的数据。
类定义
语法格式如下:
class ClassName:
类对象 类对象支持两种操作:属性引用和实例化。
属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name。
类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:
实例(Python 3.0+)
#!/usr/bin/python3
class MyClass:
"""一个简单的类实例"""
i = 12345
def f(self):
return 'hello world'
## 实例化类
x = MyClass()
## 访问类的属性和方法
print("MyClass 类的属性 i 为:", x.i)
print("MyClass 类的方法 f 输出为:", x.f())
以上创建了一个新的类实例并将该对象赋给局部变量 x,x 为空的对象。
执行以上程序输出结果为:
MyClass 类的属性 i 为: 12345
MyClass 类的方法 f 输出为: hello world
类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:
def __init__(self):
self.data = []
类定义了 __init__() 方法,类的实例化操作会自动调用 __init__() 方法。如下实例化类 MyClass,对应的 __init__() 方法就会被调用:
x = MyClass()
当然, __init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。例如:
实例(Python 3.0+)
#!/usr/bin/python3
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i) ## 输出结果:3.0 -4.5
self代表类的实例,而非类
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
class Test:
def prt(self):
print(self)
print(self.__class__)
t = Test()
t.prt()
以上实例执行结果为:
<__main__.Test instance at 0x100771878>
__main__.Test
从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。
self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的:
class Test:
def prt(runoob):
print(runoob)
print(runoob.__class__)
t = Test()
t.prt()
以上实例执行结果为:
<__main__.Test instance at 0x100771878>
__main__.Test
类的方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。
实例(Python 3.0+)
#!/usr/bin/python3
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
实例化类
p = people('runoob',10,30)
p.speak()
执行以上程序输出结果为:
runoob 说: 我 10 岁。
继承
Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。派生类的定义如下所示:
class DerivedClassName(BaseClassName1):
<statement-1>
.
.
.
<statement-N>
BaseClassName(示例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:
class DerivedClassName(modname.BaseClassName):
实例(Python 3.0+)
#!/usr/bin/python3
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
s = student('ken',10,60,3)
s.speak()
执行以上程序输出结果为:
ken 说: 我 10 岁了,我在读 3 年级
多继承
Python同样有限的支持多继承形式。多继承的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
实例(Python 3.0+)
#!/usr/bin/python3
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
#另一个类,多重继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))
#多重继承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)
test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默认调用的是在括号中排前地父类的方法
执行以上程序输出结果为:
我叫 Tim,我是一个演说家,我演讲的主题是 Python
方法重写
如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:
实例(Python 3.0+)
#!/usr/bin/python3
class Parent: ## 定义父类
def myMethod(self):
print ('调用父类方法')
class Child(Parent): ## 定义子类
def myMethod(self):
print ('调用子类方法')
c = Child() ## 子类实例
c.myMethod() ## 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
super() 函数是用于调用父类(超类)的一个方法。
执行以上程序输出结果为:
调用子类方法
调用父类方法
更多文档:
类属性与方法
类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
类的方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。
self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定是用 self。
类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods。
实例
类的私有属性实例如下:
实例(Python 3.0+)
#!/usr/bin/python3
class JustCounter:
__secretCount = 0 ## 私有变量
publicCount = 0 ## 公开变量
def count(self):
self.__secretCount += 1
self.publicCount += 1
print (self.__secretCount)
counter = JustCounter()
counter.count()
counter.count()
print (counter.publicCount)
print (counter.__secretCount) ## 报错,实例不能访问私有变量
执行以上程序输出结果为:
1
2
2
Traceback (most recent call last):
File "test.py", line 16, in <module>
print (counter.__secretCount) ## 报错,实例不能访问私有变量
AttributeError: 'JustCounter' object has no attribute '__secretCount'
类的私有方法实例如下:
实例(Python 3.0+)
#!/usr/bin/python3
class Site:
def __init__(self, name, url):
self.name = name ## public
self.__url = url ## private
def who(self):
print('name : ', self.name)
print('url : ', self.__url)
def __foo(self): ## 私有方法
print('这是私有方法')
def foo(self): ## 公共方法
print('这是公共方法')
self.__foo()
x = Site('菜鸟教程', 'www.runoob.com')
x.who() ## 正常输出
x.foo() ## 正常输出
x.__foo() ## 报错
以上实例执行结果:
类的专有方法
名称 | 作用 |
---|---|
__init__ | 构造函数,在生成对象时调用 |
__del__ | 析构函数,释放对象时使用 |
__repr__ | 打印,转换 |
__setitem__ | : 按照索引赋值 |
__getitem__ | : 按照索引获取值 |
__len__ | : 获得长度 |
__cmp__ | : 比较运算 |
__call__ | : 函数调用 |
__add__ | : 加运算 |
__sub__ | : 减运算 |
__mul__ | : 乘运算 |
__truediv__ | : 除运算 |
__mod__ | : 求余运算 |
__pow__ | : 乘方 |
运算符重载
Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:
实例(Python 3.0+)
#!/usr/bin/python3
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
以上代码执行结果如下所示:
Vector(7,8)
针对 __str__ 方法给出一个比较直观的例子:
class people:
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return '这个人的名字是%s,已经有%d岁了!'%(self.name,self.age)
a=people('孙悟空',999)
print(a)
输出:
这个人的名字是孙悟空,已经有999岁了!
如果没有重载函数的话输出的就是一串看不懂的字符串:
<__main__.people object at 0x00000272A730D278>
最新的 Python3.7 中(2018.07.13),对类的构造函数进行了精简。
3.7 版本:
from dataclasses import dataclass
@dataclass
class A:
x:int
y:int
def add(self):
return self.x + self.y
相当于以前的:
class A:
def __init__(self,x,y):
self.x = x
self.y = y
def add(self):
return self.x + self.y
Python3 中类的静态方法、普通方法、类方法
静态方法: 用 @staticmethod 装饰的不带 self 参数的方法叫做静态方法,类的静态方法可以没有参数,可以直接使用类名调用。
普通方法: 默认有个self参数,且只能被对象调用。
类方法: 默认有个 cls 参数,可以被类和对象调用,需要加上 @classmethod 装饰器。
class Classname:
@staticmethod
def fun():
print('静态方法')
@classmethod
def a(cls):
print('类方法')
## 普通方法
def b(self):
print('普通方法')
Classname.fun()
Classname.a()
C = Classname()
C.fun()
C.a()
C.b()
反向运算符重载:
__radd__: 加运算 __rsub__: 减运算 __rmul__: 乘运算 __rdiv__: 除运算 __rmod__: 求余运算 __rpow__: 乘方
复合重载运算符:
__iadd__: 加运算 __isub__: 减运算 __imul__: 乘运算 __idiv__: 除运算 __imod__: 求余运算 __ipow__: 乘方
运算符重载的时候:
#!/usr/bin/python3
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __repr__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
if other.__class__ is Vector:
return Vector(self.a + other.a, self.b + other.b)
elif other.__class__ is int:
return Vector(self.a+other,self.b)
def __radd__(self,other):
"""反向算术运算符的重载
__add__运算符重载可以保证V+int的情况下不会报错,但是反过来int+V就会报错,通过反向运算符重载可以解决此问题
"""
if other.__class__ is int or other.__class__ is float:
return Vector(self.a+other,self.b)
else:
raise ValueError("值错误")
def __iadd__(self,other):
"""复合赋值算数运算符的重载
主要用于列表,例如L1+=L2,默认情况下调用__add__,会生成一个新的列表,
当数据过大的时候会影响效率,而此函数可以重载+=,使L2直接增加到L1后面
"""
if other.__class__ is Vector:
return Vector(self.a + other.a, self.b + other.b)
elif other.__class__ is int:
return Vector(self.a+other,self.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
print (v1+5)
print (6+v2)
关于 __name__
首先需要了解__name__ 是属于 python 中的内置类属性,就是它会天生就存在与一个 python 程序中,代表对应程序名称。
比如所示的一段代码里面(这个脚本命名为 pcRequests.py),我只设了一个函数,但是并没有地方运行它,所以当 run 了这一段代码之后我们有会发现这个函数并没有被调用。但是当我们在运行这个代码时这个代码的__name__ 的值为 __main__ (一段程序作为主线运行程序时其内置名称就是 __main__)。
import requests
class requests(object):
def __init__(self,url):
self.url=url
self.result=self.getHTMLText(self.url)
def getHTMLText(url):
try:
r=requests.get(url,timeout=30)
r.raise_for_status()
r.encoding=r.apparent_encoding
return r.text
except:
return "This is a error."
print(__name__)
结果:
__main__
Process finished with exit code 0
当这个 pcRequests.py 作为模块被调用时,则它的 __name__ 就是它自己的名字:
import pcRequestspcRequestsc=pcRequestsc.__name__
结果:
'pcRequests'
看到这里应该能明白,自己的 name 在自己用时就是 main,当自己作为模块被调用时就是自己的名字,就相当于:我管自己叫我自己,但是在朋友眼里我就是小仙女一样。
Python3 类方法总结
普通方法:对象访问
私有方法:两个下划线开头,只能在类内部访问
静态方法:类和对象访问,不能和其他方法重名,不 然会相互覆盖,后面定义的会覆盖前面的
类方法:类和对象访问,不能和其他方法重名,不然会相互覆盖,后面定义的会覆盖前面的
多继承情况下:从左到右查找方法,找到为止,不然就抛出异常
class People:
## 定义基本属性
name=''
age=0
## 定义私有属性外部无法直接访问
__weight=0
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s say : i am %d."%(self.name,self.age))
p = People('Python',10,20)
p.speak()
## __weight无法直接访问
print(p.name,'--',p.age)#,'--',p.__weight)
继承
单继承:
class Student(People):
grade=''
def __init__(self,n,a,w,g):
People.__init__(self,n,a,w)
self.grade = g
## 覆写父类方法
def speak():
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
class Speak():
topic=''
name=''
def __init__(self,n,t):
self.name = n
self.topic = t
## 普通方法,对象调用
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))
## 私有方法,self调用
def __song(self):
print('唱一首歌自己听',self);
## 静态方法,对象和类调用,不能和其他方法重名,不然会相互覆盖,后面定义的会覆盖前面的
@staticmethod
def song():
print('唱一首歌给类听:静态方法');
## 普通方法,对象调用
def song(self):
print('唱一首歌给你们听',self);
## 类方法,对象和类调用,不能和其他方法重名,不然会相互覆盖,后面定义的会覆盖前面的
@classmethod
def song(self):
print('唱一首歌给类听:类方法',self)
多继承:
class Sample(Speak,Student):
a = ''
def __init__(self,n,a,w,g,t):
Student.__init__(self,n,a,w,g)
Speak.__init__(self,n,t)
test = Sample('Song',24,56,7,'Python')
test.speak()
test.song()
Sample.song()
Sample.song()
test.song()
## test.__song() 无法访问私有方法
所有专有方法中,__init__()要求无返回值,或者返回 None。而其他方法,如__str__()、__add__()等,一般都是要返回值的,如下所示:
>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
... return 'hello'
...
>>> x = Complex(3.0, -4.5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() should return None, not 'str'
而对于 __str__()、__add__() 等。
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __repr__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
if other.__class__ is Vector:
return Vector(self.a + other.a, self.b + other.b)
elif other.__class__ is int:
return Vector(self.a+other,self.b)
类的专有方法中,也是存在默认优先级的,多个方法都有返回值,但一般优先取 __str__() 的返回值,如下面例子:
class Vector: def init(self, a, b): self.a = a self.b = b def repr(self): return ‘Vector (%d, %d)’ % (self.b, self.a) def str(self): return ‘Vector (%d, %d)’ % (self.a, self.b) def add(self,other): return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
print (v1)
结果是 Vector(2,10),而不是 Vector(10,2)。这里优先使用 __str__() 的返回值。
v1.__repr__()
结果是:Vector(10,2)
在 Python 中,方法分为三类实例方法、类方法、静态方法。三者区别看代码如下:
class Test(object):
def InstanceFun(self):
print("InstanceFun");
print(self);
@classmethod
def ClassFun(cls):
print("ClassFun");
print(cls);
@staticmethod
def StaticFun():
print("StaticFun");
t = Test();
t.InstanceFun(); ## 输出InstanceFun,打印对象内存地址“<__main__.Test object at 0x0293DCF0>”
Test.ClassFun(); ## 输出ClassFun,打印类位置 <class '__main__.Test'>
Test.StaticFun(); ## 输出StaticFun
t.StaticFun(); ## 输出StaticFun
t.ClassFun(); ## 输出ClassFun,打印类位置 <class '__main__.Test'>
Test.InstanceFun(); ## 错误,TypeError: unbound method instanceFun() must be called with Test instance as first argument
Test.InstanceFun(t); ## 输出InstanceFun,打印对象内存地址“<__main__.Test object at 0x0293DCF0>”
t.ClassFun(Test); ## 错误 classFun() takes exactly 1 argument (2 given)
可以看到,在 Python 中,两种方法的主要区别在于参数。实例方法隐含的参数为类实例 self,而类方法隐含的参数为类本身 cls。
静态方法无隐含参数,主要为了类实例也可以直接调用静态方法。
所以逻辑上类方法应当只被类调用,实例方法实例调用,静态方法两者都能调用。主要区别在于参数传递上的区别,实例方法悄悄传递的是self引用作为参数,而类方法悄悄传递的是 cls 引用作为参数。
Python 实现了一定的灵活性使得类方法和静态方法,都能够被实例和类二者调用。
类的二元方法运算符重载的反向方法
类的二元方法运算符重载介绍的并不全面,文中介绍的全是正向方法,其实还有反向方法,就地方法。下面补充一些。
当解释器碰到 a+b 时,会做以下事情:
从 a 类中找 __add__ 若返回值不是 NotImplemented, 则调用 a.__add__(b)。
若 a 类中没有 __add__ 方法,则检查 b 有没有 __radd__ 。如果如果有,则调用 b.__radd__(a),若没有,则返回 NotImplemented。
接上条,若 b 也没有__radd__ 方法,则抛出 TypeError,在错误消息中知名操作数类型不支持。
比如:向量类
>>>a = Myvector([1,2,3])
>>>print(a.value)
[1,2,3]
>>>b=3
>>>c = a*b #此时调用Myvector.__mul__()
>>>print(c.value)
[3,6,9]
>>> d=b*a #这句会出错。
期望得到 b*a 也返回一个向量,b*a 应该等于 a*b。此时就需要在 Myvector 类中定义一个__rmul__方法。
def __rmul__(self, other):
if isinstance(other, int):
return Myvector([a*other for a in self.m])
每个运算符都有正向方法重载,反向方法重载。有一些有就地方法(即不返回新的对象,而是修改原本对象)。
__str__函数
str 是一个类的方法,在打印类对象,获取其属性信息时调用。打印一个实例化对象时,默认打印的其实时一个对象的地址,但是我们可以对其进行重载,打印我们想要的信息。例如上面的例子中进行的重载。
self
在类的方法中直接修改 self 是无效操作,即使 self 变量的地址与实例地址相同:
class C:
def __init__(self, a):
self.a = a
def construct(self, a):
c = C(a)
self = c
def getid(self):
return id(self)
if __name__ == '__main__':
c1 = C(2)
c1.construct(3) ## c1.a == 2
print(id(c1) == c1.getid()) ## True
class
事实上 class 的私有属性在外部也是可以访问的我们可以看下文中的例子。
#!/usr/bin/python3
class People:
def __init__(self, name, age, ):
self.name = name
self.age = age
self.__privater_var = 10
def intro(self):
print(f'My name is {self.name},I\'m {self.age}')
def get_var(self):
print(self.__privater_var)
def set_var(self, var):
self.__privater_var = var
someone = People(name='jack', age=20)
someone.intro()
print(someone.age)
someone.get_var() ## 通过get_var方法访问私有属性__privater_var,值为10
someone.set_var(30) ## 通过set_var方法修改私有属性__privater_var,值为30
someone.get_var() ## 再次通过get_var方法访问私有属性__privater_var,值为30
结果:
My name is jack,I'm 20
20
10
30
接下下来看看为什么我们使用someone.__privater_var会报错呢?
AttributeError: 'People' object has no attribute '__privater_var'
这里我们先使用 dir() 函数:
print(dir(someone)) ## 获得当前someone的属性列表
结果:
['_People__privater_var', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'get_var', 'intro', 'name', 'set_var']
从打印出的结果中,我们并没有找到'_peivater_var'但是我们看到一个'_People__privater_var'.有没有想到什么?原来是被重命名了。好,我们来试试:
print(someone._People__privater_var)
someone._People__privater_var = 40
print(someone._People__privater_var)
结果:
30
40
所以说,私有变量的属性是可以修改的。既然Python阻止访问,一般情况下就不要访问。
Python3 命名空间和作用域
命名空间
先看看官方文档的一段话:
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。
命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。
命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。
我们举一个计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。
一般有三种命名空间:
内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
命名空间查找顺序:
假设我们要使用变量 runoob,则 Python 的查找顺序为:局部的命名空间去 -> 全局命名空间 -> 内置命名空间。
如果找不到变量 runoob,它将放弃查找并引发一个 NameError 异常:
NameError: name ‘runoob’ is not defined。 命名空间的生命周期:
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。
因此,我们无法从外部命名空间访问内部命名空间的对象。
实例
## var1 是全局名称
var1 = 5
def some_func():
## var2 是局部名称
var2 = 6
def some_inner_func():
## var3 是内嵌的局部名称
var3 = 7
如下图所示,相同的对象名称可以存在于多个命名空间中。
作用域
A scope is a textual region of a Python program where a namespace is directly accessible. “Directly accessible” here means that an unqualified reference to a name attempts to find the name in the namespace.
作用域就是一个 Python 程序可以直接访问命名空间的正文区域。
在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种,分别是:
有四种作用域:
L(Local):最内层,包含局部变量,比如一个函数/方法内部。 E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。 G(Global):当前脚本的最外层,比如当前模块的全局变量。 B(Built-in): 包含了内建的变量/关键字等。,最后被搜索 规则顺序: L –> E –> G –>gt; B。
在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。
g_count = 0 ## 全局作用域
def outer():
o_count = 1 ## 闭包函数外的函数中
def inner():
i_count = 2 ## 局部作用域
内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它。在Python3.0中,可以使用以下的代码来查看到底预定义了哪些变量
>>> import builtins
>>> dir(builtins)
Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,如下代码:
>>> if True:
... msg = 'I am from Runoob'
...
>>> msg
'I am from Runoob'
>>>
实例中 msg 变量定义在 if 语句块中,但外部还是可以访问的。
如果将 msg 定义在函数中,则它就是局部变量,外部不能访问:
>>> def test():
... msg_inner = 'I am from Runoob'
...
>>> msg_inner
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'msg_inner' is not defined
>>>
从报错的信息上看,说明了 msg_inner 未定义,无法使用,因为它是局部变量,只有在函数内可以使用。
全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:
实例(Python 3.0+)
#!/usr/bin/python3
total = 0 ## 这是一个全局变量
## 可写函数说明
def sum( arg1, arg2 ):
#返回2个参数的和."
total = arg1 + arg2 ## total在这里是局部变量.
print ("函数内是局部变量 : ", total)
return total
#调用sum函数
sum( 10, 20 )
print ("函数外是全局变量 : ", total)
以上实例输出结果:
函数内是局部变量 : 30
函数外是全局变量 : 0
global 和 nonlocal关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了。
以下实例修改全局变量 num:
实例(Python 3.0+)
#!/usr/bin/python3
num = 1
def fun1():
global num ## 需要使用 global 关键字声明
print(num)
num = 123
print(num)
fun1()
print(num)
以上实例输出结果:
1
123
123
如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了,如下实例:
实例(Python 3.0+)
#!/usr/bin/python3
def outer():
num = 10
def inner():
nonlocal num ## nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
outer()
以上实例输出结果:
100
100
另外有一种特殊情况,假设下面这段代码被运行:
实例(Python 3.0+)
#!/usr/bin/python3
a = 10
def test():
a = a + 1
print(a)
test()
以上程序执行,报错信息如下:
Traceback (most recent call last):
File "test.py", line 7, in <module>
test()
File "test.py", line 5, in test
a = a + 1
UnboundLocalError: local variable 'a' referenced before assignment
错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。
修改 a 为全局变量,通过函数参数传递,可以正常执行输出结果为:
实例(Python 3.0+)
#!/usr/bin/python3
a = 10
def test(a):
a = a + 1
print(a)
test(a)
执行输出结果为:
作用域
作为一个从 Java 过来的人来说,Python 的作用域有点奇葩,作为开发者,只需要关注全局作用域和局部作用域就好了,全局作用域就是说白了模块(单个 .py 文件中)直接声明的变量。
比如有个 demo.py 的文件,含有以下代码:
var_global='global_var'#这个var_global就是全局作用域
def globalFunc():
var_local='local_var' #这个就是局部变量
class demo():
class_demo_local_var='class member' #这里也是局部变量
def localFunc(self):
var_locals='local_func_var'#这里也是局部变量
以上只是说明了全局变量仅仅是在 .py 文件中直接声明的变量叫全局变量,还有在 .py 文件中直接写的逻辑代码块中,也是全局变量。也就是说在 if/elif/else/、try/except、for/while 等逻辑代码块中的变量。
这个教程中在介绍三种命令空间的时候,说查找变量的顺序为局部的命名空间去 -> 全局命名空间 -> 内置命名空间,但是我理解的变量查找顺序为:当前域 -> 外部域(如果有) -> 全局域 -> 内置域。
光说没有什么概念,我们来一段代码就清楚了。
我们以 demo1.py 为例子:
global_var='this var on global space'
'''
申明global_var这个位置就是全局域,也就是教程中所说的全局作用域,
同时它也是直接声明在文件中的,而不是声明在函数中或者类中的变量
'''
class demo():
class_demo_local_var='class member'
'''
虽然class_demo_local_var在这里是局部变量,这个局部变量的域相对于var_locals是外部域,
所以可以直接被var_locals所在的更小的局部域访问
'''
def localFunc(self):
var_locals='local_func_var'
'''
这里也是局部变量,但是相对于class_demo_local_var变量,却是更小的域,
因此class_demo_local_var所在的哪个域无法访问到当前域来
'''
print(self.class_demo_local_var)#到这里会查找当前域中有没有class_demo_local_var这个变量,然后再到相对于当前域的外部域去查找变量
变量查找顺序
教程中写到 Python 中变量的查找顺序:“在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再去内置中找。可以看一个具体的例子。
Python 的一个内建值 int,我们首先将其赋值为 0,然后定义一个函数 fun1()。
>>> int = 0
>>> def fun1():
int = 1
def fun2():
int = 2
print(int)
fun2()
函数 fun1() 的作用就是调用函数 fun2() 来打印 int 的值。
调用函数 fun1():
>>> fun1()
2
因为 local 中的 int = 2,函数将其打印出来。
将函数 fun2() 中的 int = 2 删除:
>>> int = 0
>>> def fun1():
int = 1
def fun2():
print(int)
fun2()
调用函数 fun1():
>>> fun1()
1
因为 local 找不到 int 的值,就去上一层 non-local 寻找,发现 int = 1 并打印。
而进一步删除函数 fun1() 中的 int = 1:
>>> int = 0
>>> def fun1():
def fun2():
print(int)
fun2()
调用函数 fun1():
>>> fun1()
0
因为 local 和 non-local 都找不到 int 的值,便去 global 中寻找,发现 int = 0 并打印。
若删除 int = 0这一条件:
>>>del int
>>>def fun1():
def fun2():
print(int)
fun2()
调用函数 fun1():
>>> fun1()
<class 'int'>
因为 local、non-local、global 中都没有 int 的值,便去 built-in 中寻找 int 的值,即:
>>> int
<class 'int'>