函数最重要的目的是方便我们重复使用相同的一段程序。将一些操作隶属于一个函数,以后你想实现相同的操作的时候,只用调用函数名就可以,而不需要重复敲所有的语句。

def my_len():           def   关键字
    li=[1,2,3,4,5,6]
    count = 0
    for i in li:
        count+=1
    return count      return 关键字
set=my_len()        函数的调用以及返回值的接收
print(set)

写函数时最好不要在函数中打印,要尽量以功能为导向。

返回值:

一 没有返回值的情况:

1.不写return

二 有返回值的情况:

return 的作用:1。返回一个或多个值

       2. 终止一个函数的继续。 这点很重要好多人都没有注意到这一点,有必要举个例子

举个例子:

def my_len():
    print(1)
    print(2)
    return None    #函数运行到return这里会直接跳出函数
    print(33)
print(44)
print(my_len())

结果:

44               ###为什么会先打印出44,因为程序第一步先运行到def这行,接着运行print(44)这行,而不是运行print(1),因为函数还没有调用。
1
2
None

 

1.返回一个值:什么数据类型都可以返回,返回什么就接收什么。

2.返回多个值:

如果用一个变量接收返回值,它会返回一个元祖,为什么会返回 一个元组,这里用到了python解析包的概念。python有一种一次给多个变量赋值的方法称为序列解包。

范例1

>>> 1,3           输入
(1, 3)               结果
>>> 2,3,6        输入
(2, 3, 6)            结果
x,y,z=1,2,3
print(x,y,z)           两个变量之间调换
x,y=y,x
print(x,y,z)

它允许函数返回一个以上的值并且打包成元组,然后通过一个赋值语句很容易进行访问。所解包的序列中的元素数量必须和放置在赋值符号(=)左边的变量数量完全一致,否则Python会在赋值时引发异常,

范例2:

def my_len():
    return 2, 3,4
set1,set2,set3=my_len()
print(set1,set2 ,set3)

结果:

(2, 3,4)

返回值用多个变量接收,那么返回值的个数应该和接收值的个数一样,否则会报错。

范例一:

def my_len():
    return 2, 3,4
set1,set2,set3=my_len()
print(set1,set2 ,set3)

结果:

2 3 4

参数

def func(lst):             接收参数叫形参
    count=0
    for i in lst:
        count+=1
    return count
l=[1,2,3,4,5] 
print(func(l))           传入参数叫实参

 

传函数可以传任意数据类型,并且传什么接收什么。

传参方式

站在传参数的角度上,一共有两种传参数的方式。

第一种:按照位置传参数。

第二种:按照关键字传参数。

传参顺序,这两种方式可以混用,但是注意位置传参不可以放在关键字传参之后。。一个位置只能接受一个参数,不允许接收多个参数。

接收参数方式

站在接收参数的角度上形参有4中参数

方式一:位置参数 。这是必传的

方式二:默认参数。可以传可以不传,不传就用自己的。

方式三:*args. 可以接收多个按位置传参

方式四:**kwargs可以接收多个按关键字传参

同时*args和**kwargs,又叫做动态参数

接收参数顺序:位置参数 ,*args,默认参数,**kwargs 

参数的接收和传入的顺序:位置参数,*args,  默认参数,**kwargs    注意不要把默认参数想象成关键字参数了。

def f(a,*args,b=8,**kwargs):
    print(a)
    print('*args',args)
    print('b',b)
    print('**kwargs',kwargs)

f(5,6,7,b=8,c=9,d=10)

结果:

5
*args (6, 7)
b 8
**kwargs {'c': 9, 'd': 10}

默认参数

默认参数:python为了简化函数的调用,提供了默认参数机制。

注意:1.默认参数必须放在必选参数之后,否则python会报错。

   2.默认参数必须指向的是不变的对象。看下边的默认参数的坑

范例

def classmate(name,sex=""):
    print("姓名:%s,性别:%s"%(name,sex))
    return
classmate("小红")
classmate("小花")
classmate("小蓝","")

结果:

姓名:小红,性别:男
姓名:小花,性别:男
姓名:小蓝,性别:女

默认参数的坑:(默认参数尽量不要用可变参数)

如果默认参数是可变对象时,每次调用函数,都是用的一个参数也就是一个id,所以只要你修改了默认参数的值,就相当于全部修改了.

 范例:

def add_end(L=[]):
    L.append('we')
    return L
print(add_end())
print(add_end())
print(add_end([1]))#重新给L赋值了,相当于又另外开辟了一块内存空间.
print(add_end())#还是原来的L的内存空间

结果
['we'] ['we', 'we'] [1, 'we'] ['we', 'we', 'we']

纳尼,为什么不一直是['we']?

下边我们来分析一下这个问题,这次我是真的明白了,哈哈.

我们来看一个列子:

def f(a,b=100):
    print(f"更新前a={a},b={b},a的内存地址为{id(a)},b的内存地址为{id(b)} " )

    b += 1  # 重新给b赋值,相当于又开辟了一块内存空间
    print(f"更新后a={a},b={b},a的内存地址为{id(a)},b的内存地址为{id(b)}" )


f(1)
f(2)

结果:

# 第一次调用结果
更新前a=1,b=100,a的内存地址为4320749632,b的内存地址为4320752800 
更新后a=1,b=101,a的内存地址为4320749632,b的内存地址为4320752832

##第二次调用结果
更新前a=2,b=100,a的内存地址为4320749664,b的内存地址为4320752800 
更新后a=2,b=101,a的内存地址为4320749664,b的内存地址为4320752832

我们从上边这个列子中可以看出两个变化调用两次该函数,

  第一个变化更新前的位置参数的id是会变化的,具体原因我分析当传入一个位置参数相当于重新赋值,即开辟新的内存空间.

  第二个变化 更新前的默认参数的id地址不会发生变化,始终是用的一个id.当定义函数的时候默认参数的id就已经固定了,不管你调用多少次函数.这也就解释了为什么当默认参数是可变数据类型的时候,你只修改了默认参数,会对后续的调用有影响,当默认参数不是可变数据类型的时不会对后续调用有影响,这是因为不可变的数据类型没有修改的方法.

 

解决方法:

利用当定义函数的时候默认参数的id就已经固定了这一特性,我们不用定义函数时的那个默认参数的id了,我们重新给该默认参数赋值改变他的id地址.因为每次调用的时候,默认参数还是原来的id地址,也就睡默认参数的值不变,我们可以根据该默认参数的值,来给默认参数重新赋值上一个可变的数据类型.这样就不拿取原来的id上的值了.

将默认参数为可变数据类型改为:None,然后判断默认参数是不是None,如果是None的话就设为[]

def add_end(l=None):
    if not l:
        l = []
    print(id(l))
    l.append('we')
    return l


print(add_end())
print(add_end())
print(add_end([1]))  # 重新给L赋值了,相当于又另外开辟了一块内存空间.
print(add_end())  # 还是原来的L的内存空间

结果:

4599886088
['we']
4599886088
['we']
4599886088
[1, 'we']
4599886088
['we']

 

 

动态参数

python中*和**的作用:

 *args: 接收所有按位置传的参数。中的*要求python创建一个名为args的空元祖,然后将接受到的值封装到这个元组中.   

def f(*w):
    print(w)
f(1,3,4,5)

结果:

(1, 3, 4, 5)

 

**kwargs 接受所有按关键字传的参数,它中的**要求python创建一个名为kwargs的空字典,然后将所有的值都封装到这个字典中.

def f(**w):
    print(w)
f(a=1,b=2,c=3)

结果:

{'a': 1, 'b': 2, 'c': 3}

 

*分为两种用法:

1.当作为接受参数时,把传进来的参数放在一个元组中,注意:如果你传进来一个列表[1,2,3,4,5],print(args )后你会得到元组([1,2,3,4,5], )而不是元组(1,2,3,4,5),它并不会迭代的添加,所以for循环的时候只能循环1个数。

def f(*w):
    print(w)

l = [1, 2, 3, 4]
f(5,6,7,8,8)

 

结果:

(5, 6, 7, 8, 8)

 

2.当作为传入参数时,把传进来的迭代的参数拆开,这也就是所谓的解包(Unpacking)

def f(*w):
    print(w)

l=[1,2,3,4]
print(*l) f(
*l)

结果:

1 2 3 4
(1, 2, 3, 4)

 

解包:把序列或映射中的每个元素提取出来。注意*后面跟序列解包成位置参数,**后面跟字典,解包成关键字参数。

def f(**kwargs):
    print(kwargs)


d = {"hello": "world", "python": "rocks"}

f(**d)

结果:

{'hello': 'world', 'python': 'rocks'}

 

打包: *args 或**kwargs把*解包传进来的值,打包成一个元组字典.

 

谓的解包(Unpacking)实际上可以看做是去掉()的元组或者是去掉{}的字典。这一语法也提供了一个更加 Pythonic 地合并字典的方法:

user = {'name': "Trey", 'website': "http://treyhunner.com"}
defaults = {'name': "Anonymous User", 'page_name': "Profile Page"}

print({**defaults, **user})

 

{'page_name': 'Profile Page', 'name': 'Trey', 'website': 'http://treyhunner.com'

范例要求:写一个函数用来模拟len()的用法:

加*的情况:


def my_len(*args):
print("*args1",*args)
count=0
for i in args:
print("*args2",args)
count+=1
print(count)
my_len(*[1,2,3,4])
 

结果:

*args1 1 2 3 4               其实这里是一个元组(1,2,3,4)
*args2 (1, 2, 3, 4)
*args2 (1, 2, 3, 4)
*args2 (1, 2, 3, 4)
*args2 (1, 2, 3, 4)
4

不加*的情况:

def my_len(*args):
    print("*args",*args)
    count=0
    for i in args:
        print("*args",args)
        count+=1
    print(count)
my_len([1,2,3,4])

结果:


*args [1, 2, 3, 4]    其实这里得到的是一个元组([1, 2, 3, 4] ,)
*args ([1, 2, 3, 4],)
1


**kwargs: 接收所有按关键字传的参数。返回的是一个字典。注意在函数中用的时候不用加**,只需要kargs.

命名空间

我们给这个“存放名字与值的关系”的空间起了一个名字——叫做命名空间

代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间,在函数的运行中开辟的临时的空间叫做局部命名空间, 内边的东西只能在局部使用

命名空间的本质:存放名字与值的绑定关系的一个区域

局部命名空间(局部变量):当函数运行起来时创建的存放函数中变量和值关系的空间。

全局命名空间(全局变量):存放定义在函数外边的的变量和函数名的空间。这个全局命名空间必须在调用函数()的前面出现,否则就不是一个全局变量,会报错。

内置命名空间:存放python解释器刚启动就能运行的变量和函数名的空间。不用你自己定义,直接就能用,比如len(),int(),但是import os 不是内置命名函数

各个变量的加载顺序; python解释器运行起来》内置变量》全局变量》局部变量(在调用函数的时候产生,并且随着调用的结束而消失)。

Python查找变量的遵循LEGB原则,具体来讲就是局部变量>外层函数变量(例如闭包的最外层)>全局变量>Python的内置变量,前一层找不到的变量会去下一次去找

作用域

作用域:作用范围。

全局作用域:内置变量和全局变量的使用的区域叫做全局作用域 也就是可以在全局使用.。

局部作用域:局部变量可以使用的区域仅能在局部使用。

注意:全局变量不可以使用局部变量,局部变量可以使用全局变量,但是不能修改全局变量(后边这句话有歧义)。

如果你传入的参数对象或引用的全局变量是可变对象:列表,字典,如果参数在函数体内被修改(包括添加删除更新),那么全局变量也会被修改。
如果你传入的参数对象或引用的全局变量是不可变的对象:数字,元组,字符串。那么源对象是不会改变的

 例子1:

d = [2,3]
def f():
    d.append(66)
f()
print(d)

结果:

[2, 3, 66]

 

列子2

dic = 5
def f():
    dic = dic+1
f()
print(dic)

结果:

 5

 

局部作用域还可以嵌套更小的局部作用域。

作用域链:小范围作用域可以使用大范围的作用域,但是作用域链是单向的。

如何用一个全局变量函数调用一个局部变量函数? 

运用函数嵌套可以解决这个问题,看范例

范例一

def func1():
    def func2():
        n=8
        print(n)
    func2()
func1()                   #全局函数来调用局部变量

 

结果;

8

globals和locals 

globals():显示保存在全局作用域中的变量名和值。不管位置在哪里。

locals():local中的内容会根据它的位置来决定作用域中的内容,在局部作用域时,显示局部作用域中的变量名和值,在全局作用域中等于globasl。

范例

n=1
m=10
def func():
    a=10
    b=20
    print("locals",locals())
print("globals",globals())

func()

 

结果:

globals {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002015AEBB278>, 

'__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__':

'C:/Users/stickerzhang/PycharmProjects/untitled3/text1/tt/text2.py', '__cached__': None, 'foo': <function foo at 0x000002015AF8E488>,
'outer': <function outer at 0x000002015AF8E510>, 'n': 1, 'm': 10, 'func': <function func at 0x000002015AF8E7B8>}
locals {
'b': 20, 'a': 10}

 

范例二:

n=1
m=10
def func():
    a=10
    b=20
    print("globals",globals())
print("global",locals())
func()

结果:

globals '__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000014D5456A1D0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/张守业/PycharmProjects/untitled/day9/global.py', '__cached__': None, 'n': 1, 'm': 10, 'func': <function func at 0x0000014D54486048>}
locals {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000014D5456A1D0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/张守业/PycharmProjects/untitled/day9/global.py', '__cached__': None, 'n': 1, 'm': 10, 'func': <function func at 0x0000014D54486048>}

global和nonlocal

我们知道在局部作用域中可以使用全局作用域或外函数中的变量和值,但是不能修改.global和nonlocal就是为了解决这个问题而产生的.只不过global修改的是全局变量,non'local修改的是外函数的变量

global:global声明变量的作用域为全局作用域。注意这里的global修改的是全局的变量,不是局部的。

n=4
def f():
    global n
    n=9
f()
print(n)

结果:

 9

例子二:

n =0
def
f(): n=5 def f2(): global n n=n+1 f2() print('内层的',n) f() print('最外层的',n)

结果:

内层的 5  #没有修改最外层局部的n
最外层的 1 #修改的是全局的n

那如果要修改局部的n怎么,只能用nonlocal函数

nonlocal:允许内层函数来修改外层函数的变量。python3中新添加的功能。

gblobal和nonlocal的区别?

1.global关键字可以用在任何地方,包括最上层函数中和嵌套函数中,即使之前未定义该变量,global修饰后也可以直接使用,而nonlocal关键字只能用于嵌套函数中,并且外层函数中定义了相应的局部变量,否则会发生错误

2.global修改的是全局变量,nonlocal修改的是外函数的变量

 

def f():
    n=5
    def f2():
        n=n+1 
    f2()
    print(’外层的',n)

f()

 

结果报错:

UnboundLocalError: local variable 'n' referenced before assignment

原因分析:我们知道在内函数使用外函数变量的前提是,内函数中没有该变量,如果内函数中有该变量,就不会用外函数的变量.如何判断内函数中有该变量呢?只要该变量被赋值了就说明内函数有该变量,在f2中n=n+1 当编辑器运行到第二个n的时候发现第一个n已经被赋值就不会使用f中的n变量,而是使用f2中的第一个n变量,但是我只知道一个变量不能在赋值前被引用的所以就爆发了这个错误

 

def f():
n=5 def f2(): nonlocal n n=n+1 f2() print('内层的',n) f()

 

结果:

内层的 6

 

 

函数是第一类对象:

第一类对象共有的特征:

1.函数名可以赋值给别的变量。

2.函数可以作为一个函数的参数。

3.函数可以作为一个函数的返回值

4.函数可以作为一个数据结构的元素。

范例:

def func():
    print("in the func")
    return 10
def func2(arg):
    print("arg",arg()) ###打印一个函数也是对一个函数的调用
    return arg
f=func2(func)

结果:

in the func
arg 10

 

n+=1 和n=n+1的区别

很多资料上都在讲,n+=1  和n=n+1 是相等的,最近研究了一下其实他俩还是都区别的

# 对于不可变对象+= 与+ id都会变
b = 1
print("b更新前", id(b))
b += 1
print("b更新后", id(b))

c = 100
print("c更新前", id(c))
c += 1
print("c更新后", id(c))

# 对于可变对象+= id不会变, +的id会变
b = []
print("b更新前", id(b))
b += [1]
print("b更新后", id(b))

c = [5]
print("c更新前", id(c))
c = c + [1]
print("c更新后", id(c))

结果:

#不可变对象结果
b更新前 4408461376
b更新后 4408461408
c更新前 4408464544
c更新后 4408464576

# 可变对象结果
b更新前 4544022152
b更新后 4544022152
c更新前 4579610888
c更新后 4579611208

对于可变的对象来说,我们可以看到+=是在原来的id地址上做的修改,并没有开辟新的内存空间,但是+开辟了新的内存空间

还有一点区别

x = []  # empty list
x += range(10)  # iterates over the string and appends to list
print(x)

# 结果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 当使用+时会报错
x = []  
x = x + range(10)  
print(x)

#结果
TypeError: can only concatenate list (not "range") to list

 

posted on 2017-10-31 18:37  程序员一学徒  阅读(440)  评论(1编辑  收藏  举报