函数编程

函数

3.1 Bytes类型的定义

bytes类型是指一堆字节的集合,在python中以b开头的字符串都是bytes类型

b'xe5xb0x8fxe7x8cxbfxe5x9cx88' #b开头的都代表是bytes类型,是以16进制来显示的,2个16进制代表一个字节。 utf-8是3个字节代表一个中文,所以以上正好是9个字节

3.2 Bytes类型的作用

计算机只能存储2进制。

  • 对于文字,有 gbk,utf-8,ASCII,Unicode
  • 对于图片,必须编码成Png,JPEG
  • 对于音乐,必须编码成Mp3,wav

在python中,数据转成2进制后不是直接以01010101的形式表示的,而是用一种叫bytes(字节)的类型来表示的。

>>> s = "小猿圈"
>>> s.encode("utf-8")  # 以utf-8编码 
b'xe5xb0x8fxe7x8cxbfxe5x9cx88' #b开头的都代表是bytes类型,是以16进制来显示的,2个16进制代表一个字节。 utf-8是3个字节代表一个中文,所以以上正好是9个字节

在python中,字符串必须编码成bytes后才能存到硬盘上。

在python3中文件存储的默认编码是utf-8.

'''
自行改变文件的默认编码,改成gbk格式的
'''
f = open('book.txt', 'w','gbk')

以2进制的模式操作文件

当然,在打开文件时如果你不想让open这个对象帮你自动编码,你也可以直接往文件里存入bytes数据。

f = open(file="encode_test",mode="wb") # wb以2进制模式打开文件
s = "自学编程,谁不上小猿圈".encode("utf-8")  # 自行编码
print(s )
f.write(s)
f.close()
#以下是print(s)的输出
b'xe8x87xaaxe5xadxa6xe7xbcx96xe7xa8x8bxefxbcx8cxe8xb0x81xe4xb8x8dxe4xb8x8axe5xb0x8fxe7x8cxbfxe5x9cx88'

2进制模式打开文件有

  • wb 二进制创建
  • rb 二进制读
  • ab 二进制追加

3.3 字符编码的转换

编码转换是指将一种编码转成另外一种编码,比如 utf-8 to gbk。

为何需要编码转换呢? 因为不同操作系统编码不同, utf-8在win上没办法直接看,因为windows是GBK编码的,得转成gbk。 反过来如果你的GBK字符相在LinuxMac上正常显示,就得转成utf-8编码。

编码&解码

img

>>> s.encode("utf-8")   # 编码
b'xe5xb0x8fxe7x8cxbfxe5x9cx88'
>>> s_utf8=s.encode("utf-8")
>>> 
>>> s_utf8.decode("utf-8")  #解码
'小猿圈'

在py3里,内存里的字符串是以unicode编码的,unicode的其中一个特性就是跟所有语言编码都有映射关系。所以你的utf-8格式的文件,在windows电脑上若是不能看,就可以把utf-8先解码成unicode,再由unicode编码成gbk就可以了。

img

注意,不管在Windows or Mac or Linux上,你的pycharm IDE都可以支持各种文件编码,所以即使是utf-8的文件,在windows下的pycharm里也可以正常显示

img

3.4 深浅copy(杯子问题)

  • str的赋值(=)

    •   a = 1243
        b = a
        a = 12
        a和b指向同一内存地址,a改变不影响b
      
  • list,set,dict(=)和后面函数里list,set,dict(函数传递列表,字典,集合时发生的现象)

    •   a = [1,2,3,4]
        b = a
        a.append(5)# append返回的是None,所以不能赋值
        a和b 也是指向同一内存地址,但是这个[]的内存地址没有变(杯子问题),a里面的元素变了,b也会跟着变
      
  •   data = {‘name’: ‘yekai’,
             'age': 18,
             'scores': {}
             }
      d2 = data
      data['age'] = 20
      print(d2)
    

你说d2打印的值里,age是18,还是20?

{'name': 'alex', 'age': 20, 'scores': {}}

为何是20呢? 因为d2=data相当于只是拿到了data内存地址,但data里的每个k,v都是有单独的内存的地址的。d2,data会一直共享这个dict里的数据,不会出现像之前字符串a=1,b=a, a=2, b依然等于1的情况。

如果我确实想复制一份完成的dict数据怎么办呢?

可以用浅copy语法(python自带,不用导入模块)

因为浅copy会仅复制dict的第一层数据,更深层的scores下面的值依然是共享一份。

img

注意图中的2个dict中的name都是alex,内存地址也一样,在没改前,两个name都确实指向同一个内存地址,但只要改任何一个的值,内存地址都会变更, 如age这个key一样。

深copy(必须导入python中的一个工具模块)(很少用,因为完全复制一份数据,占空间)

  • 深copy可以使dict,list,set完全独立,无论有多少层数据。

3.5 函数的定义

  • 函数是什么?

    函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,具体区别,我们后面会讲,编程中的函数在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子过程或子程序),在Pascal中叫做procedure(过程)和function,在C中只有function,在Java里面叫做method。

    定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可

    特性:

    1. 减少重复代码
    2. 使程序变的可扩展
    3. 使程序变得易维护

    语法定义

    def sayhi():#函数名
        print("Hello, I'm nobody!")
    sayhi() #调用函数
    

    可以带参数

    #下面这段代码
    a,b = 5,8
    c = a**b
    print(c)
    #改成用函数写
    def calc(x,y):
        res = x**y
        return res #返回函数执行结果
    c = calc(a,b) #结果赋值给c变量
    print(c)
    

    参数可以让你的函数更灵活,不只能做死的动作,还可以根据调用时传参的不同来决定函数内部的执行流程

    函数参数

    形参变量

    只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量

    实参

    可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先给实参赋值

    注意,参数优先级顺序是 位置参数>关键参数

    函数的顺序:位置参数,默认参数,可变参数*args(结果为元组),命名关键字参数(带参数名调用),关键字参数**kwargs(结果为字典)

函数返回值与作用域

函数外部的代码要想获取函数的执行结果,就可以在函数里用return语句把结果返回

注意

  • 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
  • 如果未在函数中指定return,那这个函数的返回值为None

3.6 全局与局部变量

name = "Alex Li"
def change_name():
    name = "金角大王,一个有Tesla的高级屌丝"
    print("after change", name)
change_name()
print("在外面看看name改了么?",name)

输出

after change 金角大王,一个有Tesla的高级屌丝
在外面看看name改了么? Alex Li

为什么在函数内部改了name的值后, 在外面print的时候却没有改呢? 因为这两个name根本不是一回事

  • 在函数中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
  • 全局变量作用域(即有效范围)是整个程序,局部变量作用域是定义该变量的函数。
  • 变量的查找顺序是局部变量>全局变量
  • 当全局变量与局部变量同名时,在定义局部变量的函数内,局部变量起作用;在其它地方全局变量起作用。
  • 在函数里是不能直接修改全局变量的

就是想在函数里修改全局变量怎么办?

name = "Alex Li"
def change_name():
    global name #声明一个全局变量
    name = "Alex 又名金角大王,爱生活、爱自由、爱姑娘"
    print("after change", name)
change_name()
print("在外面看看name改了么?", name)

global name的作用就是要在函数里声明全局变量name ,意味着最上面的name = “Alex Li”即使不写,程序最后面的print也可以打印name

传递列表、字典、集合产生的现象

d = {"name":"Alex","age":26,"hobbie":"大保健"}
l = ["Rebeeca","Katrina","Rachel"]
def change_data(info,girls):
    info["hobbie"] = "学习"
    girls.append("XiaoYun")
change_data(d,l)
print(d,l)

执行结果{‘name’: ‘Alex’, ‘age’: 26, ‘hobbie’: ‘学习’ } [‘Rebeeca’, ‘Katrina’, ‘Rachel’, ‘XiaoYun’]

不是说不能在函数里改全局变量么,怎么改了呀?

  • 和上面的一样的道理,(杯子问题),所以改了

3.7 嵌套&匿名&高阶函数

嵌套函数

函数里不仅可以写代码,还可以嵌套函数

name = "小猿圈"
def change():
    name = "小猿圈,自学编程"
    def change2():
        # global name  如果声明了这句,下面的name改的是最外层的全局变层
        name = "小猿圈,自学编程不要钱" #这句注释掉的话,下面name打印的是哪个值?
        print("第3层打印", name) 
    change2()  # 调用内层函数
    print("第2层打印", name)
change()
print("最外层打印", name)

输出

第3层打印 小猿圈,自学编程不要钱
第2层打印 小猿圈,自学编程
最外层打印 小猿圈

通过上面的例子,我们理解了,每个函数里的变量是互相独立的,变量的查找顺序也是从当前层依次往上层找。

问个哲学问题,这东西有什么用呢?哈,现在没用,不解释,长大后学了装饰器你就知道有啥用了。

匿名函数

匿名函数就是不需要显式的指定函数名

#这段代码
def calc(x,y):
    return x**y
print(calc(2,5))
#换成匿名函数
calc = lambda x,y:x**y
print(calc(2,5))

你也许会说,用上这个东西没感觉有毛方便呀, 。。。。呵呵,如果是这么用,确实没毛线改进,不过匿名函数主要是和其它函数搭配使用的呢,如下

res = map(lambda x:x**2,[1,5,7,4,8])
for i in res:
    print(i)

输出

1
25
49
16
64

总结:lambda函数就是可以接受任意多个参数(包括可选参数)并且返回单个表达式的函数。好处有以下:

  1. lambda函数比较轻便,即用即扔,适合完成只在一处使用的简单功能
  2. 匿名函数,一般用来给filter,map这样的函数式编程服务
  3. 作为回调函数,传递给某些应用,比如消息处理

高阶函数

变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

def get_abs(n):
    if n < 0 :
        n = int(str(n).strip("-"))
    return n
def add(x,y,f):
    return f(x) + f(y)
res = add(3,-6,get_abs)
print(res)

只需满足以下任意一个条件,即是高阶函数

  • 接受一个或多个函数作为输入
  • return 返回另外一个函数

3.8 函数的递归

在函数内部,可以调用其他函数。如果一个函数在内部调用自已本身,这个函数就叫做递归函数。上面我们写的这个代码就是递归

递归特性:

  1. 必须有一个明确的结束条件
  2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)

3.9 名称空间(LEGB)

如x = 1,名称空间正是存放名字x与1 绑定关系的地方

名称空间有4种:L-->E-->G-->B

  • locals:函数内部的名字空间,locals()方法可以查看
  • enclosing function:嵌套空间,
  • globals:类似全局变量,有globals()方法
  • __builtins__:内置模块空间

不同变量的作用域不同就是由这个变量所在的名称空间决定的

作用域的范围

  • 全局范围:全局存活,全局有效、
  • 局部范围:临时存活,局部有效

查看作用域的方法:globals(),locals()

3.10 闭包

闭包

闭包就是函数定义和函数表达式位于另一个函数的函数体内,内部函数可以在外部函数执行返回后被调用执行。(也就是内层函数被当成对象返回的时候夹带了外部函数的局部变量,就会形成一个闭包)

闭包的意义:返回的对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域

3.11 函数进阶-装饰器(开放--封闭)

  • 封闭:已实现的功能代码不应该被修改
  • 开放:对现有的功能的扩展开放

装饰器

3.12 列表生成式

是python内置的来创建list的生成式,可以用一行语句来代替循环来生成一个列表

语法:[要生成的元素x * x 放在前面,后面跟for循环]

>>>[x * x for x in range(1, 11) if x % 2 == 0]
结果为[4, 16, 36, 64, 100]
使用两层循环来生成全排列
>>>[m + n for m in 'ABC' for n in 'XYZ']
结果为['AX', 'AY', 'AZ', 'BX','BY', 'BZ','CX', 'CY', 'CZ']
把一个list中的所有字符串变成小写
>>>L = ['Hello', 'World', 'IBM','Apple']
>>>[s.lower() for s in L]
结果为['hello', 'world', 'ibm', 'apple']

3.13 生成器

在python中,一边循环一边计算的机制,称为生成器

 创建生成器(generator)的方法:

  1. 把一个列表生成式中的[]改成()
  2. 包含yield的关键字的函数

generator函数的调用实际返回一个generator对象。

把函数改成generator后,一般不会用next()来获取下一个返回值,而是常使用for循环来迭代。

>>>g = (x * x for x in range(10))
>>>for i in g:
   		print(i)

斐波拉契数列:除第1个和第2个数字外.任意一个数都是其前面2个数相加得到.(用列表生成式写不出来,因为没有合适的表达式)

def fib(max):
    n, a, b = 0, 0 , 1
    while n < max:
        yield b
        a, b = b, a + b
        n += 1
       return 'done'

3.14 迭代器

迭代器,可以被next()函数调用并不断返回下一个值的对象.

1.集合数据类型(list,tuple,set ,dict ,str) 2.generator,包括生成器和带yield的generator function,以上这些可直接作用于for循环的对象统称为可迭代对象(iterable),可迭代的意思就是可遍历,可循环.

generator 都是Iterable,但list,dict,str,tuple,set,虽然都是可迭代对象(iterable),但都不是迭代器.

[typora中添加数学公式](https://blog.csdn.net/Ernest_YN/article/details/84064233#0_2)

$$
interable 对象--->>
egin{cases}
list
dict
tuple
set
str
end{cases}
$$

都可以通过iter()方法转化为Iterator(迭代器)

python 中,for循环本质上就是通过不断调用next()函数来实现的

for x in [1,2,3,4,5]:
    pass

it = iter([1,2,3,4,5])
while True:
    try:
        x = next(it)
    except StopIteration:
        break

for循环的本质

总结:1.凡是可以作用于for循环的对象都是Iterable对象.2.凡是可作用于next()函数的对象都是Iterator类型,它表示一个惰性计算的序列

3.15 内置函数

Python的len为什么你可以直接用?肯定是解释器启动时就定义好了

img

内置参数详解https://docs.python.org/3/library/functions.html?highlight=built#ascii

文件操作中的不同打开方式.

文件模式

原文地址:https://www.cnblogs.com/ylkx/p/11253052.html