函数与类

函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

函数能提高应用的模块性,和代码的重复利用率。你已经知道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:

  1. 局部作用域

    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,于是访问全局变量,此时找到了并输出。

  1. 内建作用域

    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())

函数的参数分为形参和实参。

  1. 什么是形参

对于函数来说,形式参数简称形参,是指在定义函数时,定义的一个变量名。

下面的代码中,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

形参的作用:是用来接收调用函数时输入的值。

  1. 什么是实参

对于函数来说,实际参数简称实参。

是指在调用函数时传入的实际的数据,这会被绑定到函数的形参上。

函数调用时,将值绑定到变量名上,函数调用结束,解除绑定,并且实参将不再存在于程序中。

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'>
上一页
下一页