查看原文
其他

万字Python零基础入门总结

♡Tips:公众号后台回复:python,即可领取零基础学Python教程视频


1、输入输出

  • 输出

    • print('100 + 200 ='100 + 200)
    • print('The quick brown fox''jumps over''the lazy dog')
      The quick brown fox jumps over the lazy dog

      print()会依次打印每个字符串,遇到逗号“,”会输出一个空格

    • print()在括号中加上字符串,就可以向屏幕上输出指定的文字

    • print()函数也可以接受多个字符串,用逗号“,”隔开,就可以连成一串输出;

    • print()也可以打印整数,或者计算结果

  • 输入

    • name = input()
    • Python提供了一个input(),可以让用户输入字符串,并存放到一个变量里

2、缩进和注释

  • #开头的语句是注释
  • 每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块;
  • 缩进有利有弊。好处是强迫你写出格式化的代码,但没有规定缩进是几个空格还是Tab。按照约定俗成的惯例,「应该始终坚持使用4个空格的缩进」。缩进的另一个好处是强迫你写出缩进较少的代码,你会倾向于把一段很长的代码拆分成若干函数,从而得到缩进较少的代码。缩进的坏处就是“复制-粘贴”功能失效了,这是最坑爹的地方。当你重构代码时,粘贴过去的代码必须重新检查缩进是否正确。此外,IDE很难像格式化Java代码那样格式化Python代码。
  • Python程序是「大小写敏感」的,如果写错了大小写,程序会报错

3、数据类型和变量

  • 整数

    • Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样

    • dec = int(input("输入数字:"))
      print("十进制数为:", dec)
      print("转换为二进制为:", bin(dec))
      print("转换为八进制为:", oct(dec))  # 0o开头
      print("转换为十六进制为", hex(dec))  # 0x开
  • 浮点数

    • int(x [,base ])         将x转换为一个整数  
      long(x [,base ])        将x转换为一个长整数  
      float(x )               将x转换到一个浮点数  
      complex(real [,imag ])  创建一个复数  
      str(x )                 将对象 x 转换为字符串  
      repr(x )                将对象 x 转换为表达式字符串  
      eval(str )              用来计算在字符串中的有效Python表达式,并返回一个对象  
      tuple(s )               将序列 s 转换为一个元组  
      list(s )                将序列 s 转换为一个列表  
      chr(x )                 将一个整数转换为一个字符  
      unichr(x )              将一个整数转换为Unicode字符  
      ord(x )                 将一个字符转换为它的整数值  
      hex(x )                 将一个整数转换为一个十六进制字符串  
      oct(x )                 将一个整数转换为一个八进制字符串  
    • 「使用字符串格式化」

    • img
    • 「使用round内置函数」

    • x = 1234.56789
      print(format(x, '0.2f'))
      1234.57
      占位符替换内容
      %d整数
      %f浮点数
      %s字符串
      %x十六进制整数
    • dec = float(input("输入的数字"))
      print("dec*dec =",round(dec*dec,2));
    • https://www.cnblogs.com/qinchao0317/p/10699717.html

    • 保留小数点位数

    • 数值类型转换

4、字符串

  • 字符串是以单引号'或双引号"括起来的任意文本;如果'本身也是一个字符,那就可以用""括起来,比如"I'm OK"包含的字符是I'm,空格,OK这6个字符。如果字符串内部既包含'又包含"怎么办?可以用转义字符\来标识;

    转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\

    print('i\nam\nok')

    「Python还允许用r''表示''内部的字符串默认不转义」

    print(r'xxjis\nosk\t')

    如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容

    print('''zzxsx
    zxsaxa
    sasads
    s'''
    )

    • print("i'm okss")
      print('i\'m \" ok')

5、布尔值

  • 个布尔值只有TrueFalse两种值,要么是True,要么是False

  • and、 or、 not

  • print(True and True)
    print(not 1>2)

6、空值

  • None表示空值,它是一个特殊 Python 对象, None的类型是NoneType

    • print(type(None))  #<class 'NoneType'>

7、变量

  • 变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和_的组合,且不能用数字开头

  • 「变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。例如Java是静态语言」

    • int a = 123; // a是整数类型变量
      a = "ABC"; // 错误:不能把字符串赋给整型变量

8、常量

  • 所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量:

    • PI = 3.14159265359

      但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法,如果你一定要改变变量PI的值,也没人能拦住你

  • 赋值

    • a,b,c=1,2,3
      a,b=b,a+b
      print(a,b)

9、除法计算结果是浮点数

  • print(10/3)
    print(10//3)

    /除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数

    还有一种除法是//,称为地板除,两个整数的除法仍然是整数

    %为取余计算

10、Python中的is和==

  • ==是python标准操作符中的比较操作符,用来比较判断两个对象的value(值)是否相等

  • is是比较引用地址是否相等

  • Python中,万物皆对象!

    「每个对象包含3个属性,id,type,value」

    「id就是对象地址,可以通过内置函数id()查看对象引用的地址。」

    「type就是对象类型,可以通过内置函数type()查看对象的类型。」

    「value就是对象的值。」

11、编码

  • Python 3版本中,字符串是以Unicode编码的

  • 对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

    • print('\u4e2d\u6587')  #表示中文的意思

    • print(ord('a'))
      print(chr(97))

    • \u则代表unicode编码,是一个字符

  • Python对bytes类型的数据用带b前缀的单引号或双引号表示

    • print('ABC'.encode('ascii'))  //b'ABC'

      区分'ABC'b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。

  • >>> b'ABC'.decode('ascii')
    'ABC'
    >>> '中文'.encode('utf-8')
    b'\xe4\xb8\xad\xe6\x96\x87'
    >>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
    '中文'

    如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:

    >>> b'ABC'.decode('ascii')
    'ABC'
    >>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
    '中文'

    如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节:

    >>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
    '中'

    要计算str包含多少个字符,可以用len()函数:

    >>> len('ABC')
    3
    >>> len('中文')
    2

  • 由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:

    • #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      或者
      #!/usr/bin/env python3
      #coding=utf-8

12、Python3注释

  • # 这是一个注释
  • 多行注释用三个单引号 「'''」 或者三个双引号 「"""」 将注释括起来

13、运算符

  • 逻辑运算符

    • 运算符逻辑表达式描述实例
      andx and y布尔"与" - 如果 x 为 False,x and y 返回 False,否则它返回 y 的计算值。(a and b) 返回 20。
      orx or y布尔"或" - 如果 x 是 True,它返回 x 的值,否则它返回 y 的计算值。(a or b) 返回 10。
      notnot x布尔"非" - 如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。not(a and b) 返回 False
  • 位运算符

    • &按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0(a & b) 输出结果 12 ,二进制解释:0000 1100
      |按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。(a | b) 输出结果 61 ,二进制解释:0011 1101
      ^按位异或运算符:当两对应的二进位相异时,结果为1(a ^ b) 输出结果 49 ,二进制解释:0011 0001
      ~按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。「~x」 类似于 「-x-1」(~a ) 输出结果 -61 ,二进制解释:1100 0011, 在一个有符号二进制数的补码形式。
      <<左移动运算符:运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。a << 2 输出结果 240 ,二进制解释:1111 0000
      >>右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数a >> 2 输出结果 15 ,二进制解释:0000 1111
  • 赋值运算符

    • 运算符描述实例
      =简单的赋值运算符c = a + b 将 a + b 的运算结果赋值为 c
      +=加法赋值运算符c += a 等效于 c = c + a
      -=减法赋值运算符c -= a 等效于 c = c - a
      *=乘法赋值运算符c *= a 等效于 c = c * a
      /=除法赋值运算符c /= a 等效于 c = c / a
      %=取模赋值运算符c %= a 等效于 c = c % a
      **=幂赋值运算符c **= a 等效于 c = c ** a
      //=取整除赋值运算符c //= a 等效于 c = c // a
      :=海象运算符,可在表达式内部为变量赋值。「Python3.8 版本新增运算符」在这个示例中,赋值表达式可以避免调用 len() 两次:if (n := len(a)) > 10: print(f"List is too long ({n} elements, expected <= 10)")
  • 算术运算符

    • 运算符描述实例
      +加 - 两个对象相加a + b 输出结果 31
      -减 - 得到负数或是一个数减去另一个数a - b 输出结果 -11
      *乘 - 两个数相乘或是返回一个被重复若干次的字符串a * b 输出结果 210
      /除 - x 除以 yb / a 输出结果 2.1
      %取模 - 返回除法的余数b % a 输出结果 1
      **幂 - 返回x的y次幂a**b 为10的21次方
      //取整除 - 向下取接近商的整数>>> 9//2 4 >>> -9//2 -5
  • 比较运算符

    • 运算符描述实例
      ==等于 - 比较对象是否相等(a == b) 返回 False。
      !=不等于 - 比较两个对象是否不相等(a != b) 返回 True。
      >大于 - 返回x是否大于y(a > b) 返回 False。
      <小于 - 返回x是否小于y。所有比较运算符返回1表示真,返回0表示假。这分别与特殊的变量True和False等价。注意,这些变量名的大写。(a < b) 返回 True。
      >=大于等于 - 返回x是否大于等于y。(a >= b) 返回 False。
      <=小于等于 - 返回x是否小于等于y。(a <= b) 返回 True。
  • 身份运算符

    • 运算符描述实例
      isis 是判断两个标识符是不是引用自一个对象「x is y」, 类似 「id(x) == id(y)」 , 如果引用的是同一个对象则返回 True,否则返回 False
      is notis not 是判断两个标识符是不是引用自不同对象「x is not y」 , 类似 「id(a) != id(b)」。如果引用的不是同一个对象则返回结果 True,否则返回 False。
  • 成员运算符

    • 运算符描述实例
      in如果在指定的序列中找到值返回 True,否则返回 False。x 在 y 序列中 , 如果 x 在 y 序列中返回 True。
      not in如果在指定的序列中没有找到值返回 True,否则返回 False。x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True。

14、数字类型转换

  • 「int(x)」 将x转换为一个整数。
  • 「float(x)」 将x转换到一个浮点数。

15、list-列表

  • 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。

    Python有6个序列的内置类型,但最常见的是列表和元组。

    序列都可以进行的操作包括索引,切片,加,乘,检查成员。

    此外,Python已经内置确定序列的长度以及确定最大和最小的元素的方法。

  • 「列表的数据项不需要具有相同的类型」

  • list是一种有序的集合,可以随时添加和删除其中的元素。

  • 访问列表  可正负

    • list1=['1','zz','333','444']
      print(list1[0])
      print(list1[-1])

  • list是一个可变的有序表,所以,可以往list中追加元素到末尾:

    • list1.append("zzdc")
      list1.append("zdc2")
      print(list1)

      也可以把元素插入到指定的位置,比如索引号为1的位置

    • list1.insert(1,222)
      print(list1)

      要删除list末尾的元素,用pop()或pop(i)方法 pop会把删去的元素返回

    • print(list1.pop())
      print(list1.pop(0))

      要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:

    • list1[0]='mzd'
      print(list1)

      「list元素也可以是另一个list」

    • list1[0]=['1',2,3,4]
      print(list1)

      >>> p = ['asp''php']
      >>> s = ['python''java', p, 'scheme']

  • 列表脚本操作符

    • for var in list1:
          print(var)

    • Python 表达式结果描述
      len([1, 2, 3])3长度
      [1, 2, 3] + [4, 5, 6][1, 2, 3, 4, 5, 6]组合
      ['Hi!'] * 4['Hi!', 'Hi!', 'Hi!', 'Hi!']重复
      3 in [1, 2, 3]True元素是否存在于列表中
      for x in [1, 2, 3]: print(x, end=" ")1 2 3迭代

      遍历

  • 列表截取与拼接

    • 操作:

      Python 表达式结果描述
      L[2]'Taobao'读取第三个元素
      L[-2]'Runoob'从右侧开始读取倒数第二个元素: count from the right
      L[1:]['Runoob', 'Taobao']输出从第二个元素开始后的所有元素
    • 变量[头下标:尾下标]
      变量[头下标:尾下标:步长]
      list=['123','abc',0,True]
      x=list[1:]
      y=list[:3]
      z=list[2:3]
      print(x)
      print(y)
      print

      list=['123','abc',0,True]
      x=list[-3:]
      y=list[:-2]
      z=list[-3:-1]
      print(x)
      print(y)
      print(z)

      list=['123','abc',0,True,"12345"]
      x=list[1:4:2]
      print(x)


  • 列表函数&方法

    Python包含以下函数:

    序号函数
    1len(list) 列表元素个数
    2max(list) 返回列表元素最大值
    3min(list) 返回列表元素最小值
    4list(seq) 将元组转换为列表

    Python包含以下方法:

    序号方法
    1list.append(obj) 在列表末尾添加新的对象
    2list.count(obj) 统计某个元素在列表中出现的次数
    3list.extend(seq) 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
    4list.index(obj) 从列表中找出某个值第一个匹配项的索引位置
    5list.insert(index, obj) 将对象插入列表
    6[list.pop(index=-1]) 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值
    7list.remove(obj) 移除列表中某个值的第一个匹配项
    8list.reverse() 反向列表中元素
    9list.sort( key=None, reverse=False) 对原列表进行排序
    10list.clear() 清空列表
    11list.copy() 复制列表

16、元组-tuple

  • tuple和list非常类似,但是「tuple一旦初始化就不能修改」

  • 不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。

  • tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来

    • t = (12)
      t = ()  #如果要定义一个空的tuple

      要定义一个只有1个元素的tuple,如果你这么定义

      >>> t = (1)
      >>> t
      1

      定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1

      所以,「只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

      >>> t = (1,)
      >>> t
      (1,)

      一个“可变的”tuple:

      >>> t = ('a''b', ['A''B'])
      >>> t[2][0] = 'X'
      >>> t[2][1] = 'Y'
      >>> t
      ('a''b', ['X''Y'])

      这个tuple定义的时候有3个元素,分别是'a''b'和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gT70crDv-1612773531303)(1.assets/0.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qusc6dYV-1612773531305)(1.assets/0-20200713003324698.png)]

表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,「但指向的这个list本身是可变的!」

「要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变」

  • 以对元组进行连接组合

  • tup1 = (1234.56)
    tup2 = ('abc''xyz')
     
    # 以下修改元组元素操作是非法的。
    # tup1[0] = 100
     
    # 创建一个新的元组
    tup3 = tup1 + tup2

17、if else

  • if 判断条件:
        执行语句……
    else
        执行语句……

    当判断条件为多个值时

    if 判断条件1:
        执行语句1……
    elif 判断条件2:
        执行语句2……
    elif 判断条件3:
        执行语句3……
    else:
        执行语句4……

    if判断条件还可以简写,比如写:

    if x:
        print('True')

    只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False

    再议 input

    最后看一个有问题的条件判断。很多同学会用input()读取用户的输入,这样可以自己输入,程序运行得更有意思:

    birth = input('birth: ')
    if birth < 2000:
        print('00前')
    else:
        print('00后')

    输入1982,结果报错:

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unorderable types: str() > int()

    这是因为input()返回的数据类型是strstr不能直接和整数比较,必须先把str转换成整数。「Python提供了int()函数来完成这件事情:」

    s = input('birth: ')
    birth = int(s)
    if birth < 2000:
        print('00前')
    else:
        print('00后')

    再次运行,就可以得到正确地结果。但是,如果输入abc呢?又会得到一个错误信息:

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: invalid literal for int() with base 10: 'abc'

    原来int()函数发现一个字符串并不是合法的数字时就会报错,程序就退出了。

18、循环

  • while 判断条件(condition):
        执行语句(statements)……

「循环使用 else 语句」

在 python 中,while … else 在循环条件为 false 时执行 else 语句块:

  • count = 0
    while count < 5:
       print count, " is  less than 5"
       count = count + 1
    else:
       print count, " is not less than 5"

for 「循环语句」

  • for iterating_var in sequence:
       statements(s)

在 python 中,for … else 表示这样的意思,for 中的语句和普通的没有区别,「else 中的语句会在循环正常执行完(即 for 不是通过 break 跳出而中断的)的情况下执行」,while … else 也是一样。

「Python 语言允许在一个循环体里面嵌入另一个循环。」

for iterating_var in sequence:
   for iterating_var in sequence:
      statements(s)
   statements(s)
   
while expression:
   while expression:
      statement(s)
   statement(s)

19、break、continue和pass

  • Python continue 语句跳出本次循环,而break跳出整个循环。

  • Python pass 是空语句,是为了保持程序结构的完整性。

    「pass」 不做任何事情,一般用做占位语句。

    • 在 Python3.x 的时候 pass 可以写或不写。

      python2.x:

      def function():
          # 空函数在Python2.x版本中pass是必须的
          pass

      python3.x

      def function():
          # 在Python3.x的时候pass可以写或不写
          pass

20、Dictionary--字典

  • 字典是另一种可变容器模型,且可存储任意类型对象。

    字典的每个键值 「key=>value」 对用冒号 「:」 分割,每个键值对之间用逗号 「,」 分割,整个字典包括在花括号 「{}」 中 ,格式如下所示:

    d = {key1 : value1, key2 : value2 }

    如果key不存在,dict就会报错:

    >>> d['Thomas']
    Traceback (most recent call last):
      File "<stdin>", line 1in <module>
    KeyError: 'Thomas'

    要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:

    >>> 'Thomas' in d
    False

    二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:

    >>> d.get('Thomas')
    >>> d.get('Thomas'-1)
    -1

    注意:返回None的时候Python的交互环境不显示结果。

    要删除一个key,用pop(key)方法,对应的value也会从dict中删除:

    >>> d.pop('Bob')
    75
    >>> d
    {'Michael'95'Tracy'85}

    请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。

    和list比较,dict有以下几个特点:

    而list相反:

    所以,dict是用空间来换取时间的一种方法。

    「这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。」

    「要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:」

  1. 查找和插入的时间随着元素的增加而增加;
  2. 占用空间小,浪费内存很少。
  1. 查找和插入的速度极快,不会随着key的增加而变慢;
  2. 需要占用大量的内存,内存浪费多。

21、函数

  • 定义函数

    • 在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

    • def my_abs(x):
          if x >= 0:
              return x
          else:
              return -x

      「如果你已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名(不含.py扩展名):」

  • 参数的可变与不可变

    • **不可变类型:**变量赋值 「a=5」 后再赋值 「a=10」,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
    • **可变类型:**变量赋值 「la=[1,2,3,4]」 后再赋值 「la[2]=5」 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
    • **不可变类型:**类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
    • **可变类型:**类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
    • 在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。

      python 函数的参数传递:

      「python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。」

  • python 传不可变对象实例

    • #!/usr/bin/python
      # -*- coding: UTF-8 -*-
       
      def ChangeInt( a ):
          a = 10
       
      b = 2
      ChangeInt(b)
      print b # 结果是 2

  • 传可变对象实例

    • !/usr/bin/python
      # -*- coding: UTF-8 -*-
       
      # 可写函数说明
      def changeme( mylist ):
         "修改传入的列表"
         mylist.append([1,2,3,4])
         print "函数内取值: ", mylist
         return
       
      # 调用changeme函数
      mylist = [10,20,30]
      changeme( mylist )
      print "函数外取值: ", mylist

  • 命名关键字参数

    • #!/usr/bin/python
      # -*- coding: UTF-8 -*-
       
      #可写函数说明
      def printme( str ):
         "打印任何传入的字符串"
         print str
         return
       
      #调用printme函数
      printme( str = "My string")

    • 关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。

      「使用关键字参数允许函数调用时参数的顺序与声明时不一致」,因为 Python 解释器能够用参数名匹配参数值

  • 关键字参数

    **可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。**请看示例:

    def person(name, age, **kw):
        print('name:', name, 'age:', age, 'other:', kw)

    函数person除了必选参数nameage外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:

    >>> person('Michael'30)
    name: Michael age: 30 other: {}

    也可以传入任意个数的关键字参数:

    >>> person('Bob'35, city='Beijing')
    name: Bob age: 35 other: {'city''Beijing'}
    >>> person('Adam'45, gender='M', job='Engineer')
    name: Adam age: 45 other: {'gender''M''job''Engineer'}

    关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到nameage这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

    和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

    >>> extra = {'city''Beijing''job''Engineer'}
    >>> person('Jack', 24, city=extra['city'], job=extra['job'])
    name: Jack age: 24 other: {'city''Beijing''job''Engineer'}

    当然,上面复杂的调用可以用简化的写法:

    >>> extra = {'city''Beijing''job''Engineer'}
    >>> person('Jack'24, **extra)
    name: Jack age: 24 other: {'city''Beijing''job''Engineer'}

    **extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra

  • 命名关键字参数

    对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。

    仍以person()函数为例,我们希望检查是否有cityjob参数:

    def person(name, age, **kw):
        if 'city' in kw:
            # 有city参数
            pass
        if 'job' in kw:
            # 有job参数
            pass
        print('name:', name, 'age:', age, 'other:', kw)

    但是调用者仍可以传入不受限制的关键字参数:

    >>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)

    **如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数。**这种方式定义的函数如下:

    def person(name, age, *, city, job):
        print(name, age, city, job)

    「和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。」

    调用方式如下:

    >>> person('Jack'24, city='Beijing', job='Engineer')
    Jack 24 Beijing Engineer

    如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

    def person(name, age, *args, city, job):
        print(name, age, args, city, job)

    命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

    >>> person('Jack'24'Beijing''Engineer')
    Traceback (most recent call last):
      File "<stdin>", line 1in <module>
    TypeError: person() takes 2 positional arguments but 4 were given

    由于调用时缺少参数名cityjob,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。

    命名关键字参数可以有缺省值,从而简化调用:

    def person(name, age, *, city='Beijing', job):
        print(name, age, city, job)

    由于命名关键字参数city具有默认值,调用时,可不传入city参数:

    >>> person('Jack'24, job='Engineer')
    Jack 24 Beijing Engineer

    使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:

    def person(name, age, city, job):
        # 缺少 *,city和job被视为位置参数
        pass

  • 默认参数

    • 调用函数时,默认参数的值如果没有传入,则被认为是默认值。下例会打印默认的age,如果age没有被传入:

    • #!/usr/bin/python
      # -*- coding: UTF-8 -*-
       
      #可写函数说明
      def printinfo( name, age = 35 ):
         "打印任何传入的字符串"
         print "Name: ", name
         print "Age ", age
         return
       
      #调用printinfo函数
      printinfo( age=50, name="miki" )
      printinfo( name="miki" )

      「定义默认参数要牢记一点:默认参数必须指向不变对象!」

      def add_end(L=[]):
          L.append('END')
          return L
      >>> add_end()
      ['END']
      >>> add_end()
      ['END''END']
      >>> add_end()
      ['END''END''END']

      很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。

      原因解释如下:

      Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

  • 可变参数

    在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。

    我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。

    要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

    def calc(numbers):
        sum = 0
        for n in numbers:
            sum = sum + n * n
        return sum

    但是调用的时候,需要先组装出一个list或tuple:

    >>> calc([1, 2, 3])
    14
    >>> calc((1, 3, 5, 7))
    84

    如果利用可变参数,调用函数的方式可以简化成这样:

    >>> calc(1, 2, 3)
    14
    >>> calc(1, 3, 5, 7)
    84

    所以,我们把函数的参数改为可变参数:

    def calc(*numbers):
        sum = 0
        for n in numbers:
            sum = sum + n * n
        return sum

    定义可变参数和定义一个list或tuple参数相比,「仅仅在参数前面加了一个*号」。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。「但是,调用该函数时,可以传入任意个参数,包括0个参数」

    >>> calc(1, 2)
    5
    >>> calc()
    0

    如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:

    >>> nums = [1, 2, 3]
    >>> calc(nums[0], nums[1], nums[2])
    14

    这种写法当然是可行的,问题是太繁琐,「所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去」

    >>> nums = [1, 2, 3]
    >>> calc(*nums)
    14

    *nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。」

    def change(*numbers):
        sum=0;
        for i in numbers:
            sum+=i;
        return sum;
    print(change(1,2,3))
    st=(1,2,3,4,5,6,7)
    print(change(*st))

    在Python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这4种参数都可以一起使用,或者只用其中某些,但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数和关键字参数。

    def func(a, b, c=0, *args, **kw):
        print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
    >>> func(12)
    a = 1 b = 2 c = 0 args = () kw = {}
    >>> func(12, c=3)
    a = 1 b = 2 c = 3 args = () kw = {}
    >>> func(123'a''b')
    a = 1 b = 2 c = 3 args = ('a''b') kw = {}
    >>> func(123'a''b', x=99)
    a = 1 b = 2 c = 3 args = ('a''b') kw = {'x'99}

  • 匿名函数

    • lambda只是一个表达式,函数体比def简单很多。
    • lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
    • lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
    • 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
    • python 使用 lambda 来创建匿名函数。

      语法

      lambda函数的语法只包含一个语句,如下:

      lambda [arg1 [,arg2,.....argn]]:expression

      如下实例:

      实例(Python 2.0+)

      #!/usr/bin/python # -- coding: UTF-8 --  # 可写函数说明 sum = lambda arg1, arg2: arg1 + arg2  # 调用sum函数 print "相加后的值为 : ", sum( 10, 20 ) print "相加后的值为 : ", sum( 20, 20 )

      以上实例输出结果:

      相加后的值为 :  30
      相加后的值为 :  40

  • 返回多个值

    • import math

      def move(x, y, step, angle=0):
          nx = x + step * math.cos(angle)
          ny = y - step * math.sin(angle)
          return nx, ny
          
      x, y = move(10010060, math.pi / 6)

  • 全局变量和局部变量

    定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。

    局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:

    实例(Python 2.0+)

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
     
    total = 0 # 这是一个全局变量
    # 可写函数说明
    def sum( arg1, arg2 ):
       #返回2个参数的和."
       total = arg1 + arg2 # total在这里是局部变量.
       print "函数内是局部变量 : ", total
       return total
     
    #调用sum函数
    sum( 1020 )
    print "函数外是全局变量 : ", total

    「默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!」

    「要注意定义可变参数和关键字参数的语法:」

    *args是可变参数,args接收的是一个tuple;」

    kw是关键字参数,kw接收的是一个dict。

    「以及调用函数时如何传入可变参数和关键字参数的语法:」

    「可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));」

    「关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。」

    **使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

    「命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。」

    「定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。」

  • 函数别名

    • 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名

    • >>> a = abs # 变量a指向abs函数
      >>> a(-1# 所以也可以通过a调用abs函数
      1

  • Import 与 from import

    • >>> import datetime
       
      >>> print(datetime.datetime.now()


      >>> from datetime import datetime
       
      >>> print(datetime.now())

  • 包的概念     packa包中

    • from packa.a import a
      from packa.b import b
      a()
      b()

22、切片

lis=[1,2,3,4,5,6,7,8]
print(lis[0:3])
print(lis[:4])
print(lis[-5:-3])
print(lis[-5:])

23、迭代

  • 在Python中,迭代是通过for ... in来完成的

    • for num in lis:
          print(num)

  • 默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()

    • d = {'a'1'b'2'c'3}
      for k in d:
          print(k)
      for v in d.values():
          print(v)
      for k,v in d.items():
          print('k=',k,'v=',v)

  • 字符串也是可迭代对象

    • for ch in 'ABCD':
          print(ch)

  • 如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:

    • >>> from collections import Iterable
      >>> isinstance('abc', Iterable) # str是否可迭代
      True
      >>> isinstance([1,2,3], Iterable) # list是否可迭代
      True
      >>> isinstance(123, Iterable) # 整数是否可迭代
      False

  • 如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

    • for i,v in enumerate(lis):
          print(i,v)

24、列表生成式

  • 列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

  • python range() 函数可创建一个整数列表,一般用在 for 循环中。

  • print(list(range(1,20)))

    l=[]
    for i in range(1,20):
        l.append(i*i)
    print(l)

    print([x * x for x in range(1,5)]) #列表生成式

    print([x * x for x in range(110if x%2==1])

    print([d for d in os.listdir('/')])  #import os

    写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。

    「for循环后面还可以加上if判断」

    「还可以使用两层循环,可以生成全排列」

    print([x+y for x in 'abc' for y in 'bdf'])

  • 列表生产式也可以同时使用多个变量

    • d = {'x''A''y''B''z''C'}
      print([k+'='+v for k,v in d.items()])

  • 列表生成式中的if....else

    • print([[x if x % 2 == 0 else -x for x in range(111)]])

      print([x if x>2 else 100 for x in range(1,5)])

      「在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else。」

25、生成器

  • 通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

    所以,「如果列表元素可以按照某种算法推算出来」,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator

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

    generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

    我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

  • generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,「还可以用函数来实现」

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

      如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator;

      「函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。」

      def odd():
          print('step 1')
          yield 1
          print('step 2')
          yield(3)
          print('step 3')
          yield(5)

      但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中:

      >>> g = fib(6)
      >>> while True:
      ...     try:
      ...         x = next(g)
      ...         print('g:', x)
      ...     except StopIteration as e:
      ...         print('Generator return value:', e.value)
      ...         break
      ...
      g: 1
      g: 1
      g: 2
      g: 3
      g: 5
      g: 8
      Generator return value: done

26、迭代器

  • 一类是集合数据类型,如listtupledictsetstr等;

    一类是generator,包括生成器和带yield的generator function。

    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

    可以使用isinstance()判断一个对象是否是Iterable对象

  • 而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

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

  • 生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

    listdictstrIterable变成Iterator可以使用iter()函数:

    >>> isinstance(iter([]), Iterator)
    True
    >>> isinstance(iter('abc'), Iterator)
    True

    你可能会问,为什么listdictstr等数据类型不是Iterator

    这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,「所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算」

    Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

  • 「凡是可作用于for循环的对象都是Iterable类型;」

    「凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;」

    集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

    「Python的for循环本质上就是通过不断调用next()

27、函数式编程

1、高阶函数

  • 变量可以指向函数
  • 函数名也是变量
  • 既然变量可以指向函数,函数的参数能接收变量,那么「一个函数就可以接收另一个函数作为参数」,这种函数就称之为「高阶函数」

  • f=abs
    print(f)
    abs=10  #abs本来是内嵌函数
    print(abs)

    def absAdd(x,y,f):
        return f(x)+f(y)
    print(absAdd(-1,-2,f))

2、map/reduce

  • map

    • def pow(x):
          return x*x
      # 返回的是一个Iterator  因此通过list()函数让它把整个序列都计算出来并返回一个list。
      lis = map(pow, [12345])
      print(list(lis))

      print(list(map(str,[1,2,3,4,5])))   #返回字符串

  • reduce

    • from functools import reduce
      def add(x,y):
          return x+y
      print(reduce(add,[1,2,3,4,5]))

      reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,re**duce把结果继续和序列的下一个元素做累积计算**,其效果就是:

      reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

  • filter

    • def is_odd(n):
          return n % 2 == 1

      list(filter(is_odd, [1245691015]))
      # 结果: [1, 5, 9, 15]

    • Python内建的filter()函数用于过滤序列。和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

  • sorted

    • >>> sorted([365-129-21])
      [-21-125936]

      此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

      >>> sorted([365-129-21], key=abs)
      [59-12-2136]

      key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。

3、返回函数

  • def lazy_sum(*args):
        def sum():
            ax = 0
            for n in args:
                ax = ax + n
            return ax
        return sum

    f = lazy_sum(13579)
    print(f())

    当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

    「在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。」

闭包

  • 当一个函数返回了一个函数后,其内部的局部变量还被新函数引用

  • 返回的函数并没有立刻执行,而是直到调用了f()才执行

  • def count():
        fs = []
        for i in range(14):
            def f():
                 return i*i
            fs.append(f)
        return fs

    f1, f2, f3 = count()
    在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。

    你可能认为调用f1(),f2()和f3()结果应该是149,但实际结果是:

    >>> f1()
    9
    >>> f2()
    9
    >>> f3()
    9

    def count():
        fs = []
        for i in range(1, 4):
            def f():
                 return i*i
            fs.append(f)
        return fs

    f1, f2, f3 = count()

    在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。

    你可能认为调用f1()f2()f3()结果应该是149,但实际结果是:

    >>> f1()
    9
    >>> f2()
    9
    >>> f3()
    9

    全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9

    「返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。」

4、lambda

  • f=lambda x:x*x-2;
    f1=lambda x,y:x+y
    print(f(2))
    print(f1(3,4))

5、装饰器

  • def log(f):
        def decrotor(*args,**kw):
            print('call %s():' % f.__name__)
            return f(*args, **kw)
        return decrotor
    @log
    def tes():
        print('tes')
    tes()

    @log放到now()函数的定义处,相当于执行了语句:

    now = log(now)

    由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

    wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

  • 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

    def log(text):
        def decorator(func):
            def wrapper(*args, **kw):
                print('%s %s():' % (text, func.__name__))
                return func(*args, **kw)
            return wrapper
        return decorator

    这个3层嵌套的decorator用法如下:

    @log('execute')
    def now():
        print('2015-3-25')

    执行结果如下:

    >>> now()
    execute now():
    2015-3-25

    和两层嵌套的decorator相比,3层嵌套的效果是这样的:

    >>> now = log('execute')(now)

    我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

    以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'

    >>> now.__name__
    'wrapper'

    因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

    不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

    import functools

    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper

    或者针对带参数的decorator:

    import functools

    def log(text):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kw):
                print('%s %s():' % (text, func.__name__))
                return func(*args, **kw)
            return wrapper
        return decorator

    import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

6、偏函数

  • Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。

  • nt()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

    >>> int('12345')
    12345

    int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

    >>> int('12345', base=8)
    5349
    >>> int('12345', 16)
    74565

    假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

    def int2(x, base=2):
        return int(x, base)

    这样,我们转换二进制就非常方便了:

    >>> int2('1000000')
    64
    >>> int2('1010101')
    85

    functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

    >>> import functools
    >>> int2 = functools.partial(int, base=2)
    >>> int2('1000000')
    64
    >>> int2('1010101')
    85

    所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

    注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:

    >>> int2('1000000', base=10)
    1000000

    最后,创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数,当传入:

    int2 = functools.partial(int, base=2)

    实际上固定了int()函数的关键字参数base,也就是:

    int2('10010')

    相当于:

    kw = { 'base': 2 }
    int('10010', **kw)

    当传入:

    max2 = functools.partial(max, 10)

    实际上会把10作为*args的一部分自动加到左边,也就是:

    max2(5, 6, 7)

    相当于:

    args = (10, 5, 6, 7)
    max(*args)

    结果为10

28、模块

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
    args = sys.argv
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':
    test()

第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;

第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。

你可能注意到了,使用sys模块的第一步,就是导入该模块:

import sys

导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。

sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:

运行python3 hello.py获得的sys.argv就是['hello.py']

运行python3 hello.py Michael获得的sys.argv就是['hello.py', 'Michael']

最后,注意到这两行代码:

if __name__=='__main__':
    test()

**当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,**而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

私有变量和函数
  • 正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;

    「类似__xxx__这样的变量是特殊变量」,可以被直接引用,但是有特殊用途,比如上面的__author____name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;

    **类似_xxx__xxx**这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;

  • 类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;

    「之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。」

在Python中,有以下几种方式来定义变量:

  • xx:公有变量
  • _xx:单前置下划线,私有化属性或方法,类对象和子类可以访问,from somemodule import *禁止导入
  • __xx:双前置下划线,私有化属性或方法,无法在外部直接访问(名字重整所以访问不到)
  • 「xx」:双前后下划线,系统定义名字(不要自己发明这样的名字)
  • xx_:单后置下划线,用于避免与Python关键词的冲突

「使用不同方法导入模块,模块中私有变量的使用区别」

「在使用不同方法导入模块后,是否能使用模块中的私有属性和方法,有以下两种情况」

  • 在使用 from somemodule import * 导入模块的情况下,不能导入或使用私有属性和方法
  • 在使用 import somemodule 导入模块的情况下,能导入并使用私有属性和方法
第三方模块
  • 在Python中,安装第三方模块,是通过包管理工具pip完成的。

  • 模块搜索路径

    当我们试图加载一个模块时,Python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错:

    >>> import mymodule
    Traceback (most recent call last):
      File "<stdin>", line 1in <module>
    ImportError: No module named mymodule

    「默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中」

    >>> import sys
    >>> sys.path
    ['''/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip''/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', ..., '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']

    如果我们要添加自己的搜索目录,有两种方法:

    一是直接修改sys.path,添加要搜索的目录:

    >>> import sys
    >>> sys.path.append('/Users/michael/my_py_scripts')

    这种方法是在运行时修改,运行结束后失效。

    「第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。」

29、类和实例

    • class Student(object):

          def __init__(self, name, score):
              self.name = name
              self.score = score

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去

注意到**__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。**

「有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去」

「和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。」

「可以自由地给一个实例变量绑定属性」

class Student(object):
    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'


方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;

stu=Student('zdc',29)
def min(a,b):
    if a>b:
        return b
    else:
        return a
print(stu)
stu.min=min
print(stu.min(1,2))
stu.zz='ssd'
print(stu.zz)

「实例可以任意添加属性和方法」

30、访问限制

class Student(object):
    def __init__(self,name,score):
        self.__name=name
        self.__score=score

stu=Student('zdc',29)

print(stu._Student__name)#可以访问
print(stu.__name)

如果要让内部属性不被外部访问,「可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name实例变量.__score了:

>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

但是如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法:

class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法:

class Student(object):
    ...

    def set_score(self, score):
        self.__score = score

你也许会问,原先那种直接通过bart.score = 99也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

class Student(object):
    ...

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')

需要注意的是,在Python中,「变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量」,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。「不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量」

31、继承和多态

  • class Animal(object):
        def run(self):
            print('Animal is running...')

    class Dog(Animal):
        pass

    class Cat(Animal):
        pass

    dog = Dog()
    dog.run()


    继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示Dog is running...Cat is running...,因此,对DogCat类改进如下:

    class Dog(Animal):

        def run(self):
            print('Dog is running...')

    class Cat(Animal):

        def run(self):
            print('Cat is running...')

    再次运行,结果如下:

    Dog is running...
    Cat is running...

    当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

    def run_twice(animal):
        animal.run()

32、获取对象信息

  • 基本类型都可以用type()判断

    >>> type(123)
    <class 'int'>
    >>> type('str')
    <class 'str'>
    >>> type(None)
    <type(None) 'NoneType'>

    如果一个变量指向函数或者类,也可以用type()判断:

    >>> type(abs)
    <class 'builtin_function_or_method'>
    >>> type(a)
    <class '__main__.Animal'>

    但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:

    >>> type(123)==type(456)
    True
    >>> type(123)==int
    True
    >>> type('abc')==type('123')
    True
    >>> type('abc')==str
    True
    >>> type('abc')==type(123)
    False

    判断基本数据类型可以直接写intstr等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:

    >>> import types
    >>> def fn():
    ...     pass
    ...
    >>> type(fn)==types.FunctionType
    True
    >>> type(abs)==types.BuiltinFunctionType
    True
    >>> type(lambda x: x)==types.LambdaType
    True
    >>> type((x for x in range(10)))==types.GeneratorType
    True

  • 使用isinstance()

    • 对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

    • isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:

      >>> a = Animal()
      >>> d = Dog()
      >>> h = Husky()
      然后,判断:

      >>> isinstance(h, Husky)
      True
      >>> isinstance(h, Dog)
      True    #但由于Husky是从Dog继承下来的,所以,h也还是Dog类型

  • dir()

    • 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

      >>> dir('ABC')
      ['__add__''__class__',..., '__subclasshook__''capitalize''casefold',..., 'zfill']

      仅仅把属性和方法列出来是不够的,配合**getattr()setattr()以及hasattr()**,我们可以直接操作一个对象的状态:

      >>> hasattr(obj, 'x'# 有属性'x'吗?
      True
      >>> obj.x
      9
      >>> hasattr(obj, 'y'# 有属性'y'吗?
      False
      >>> setattr(obj, 'y'19# 设置一个属性'y'
      >>> hasattr(obj, 'y'# 有属性'y'吗?
      True
      >>> getattr(obj, 'y'# 获取属性'y'
      19
      >>> obj.y # 获取属性'y'
      19

      如果试图获取不存在的属性,会抛出AttributeError的错误:

      >>> getattr(obj, 'z'# 获取属性'z'
      Traceback (most recent call last):
        File "<stdin>", line 1in <module>
      AttributeError: 'MyObject' object has no attribute 'z'

      可以传入一个default参数,如果属性不存在,就返回默认值:

      >>> getattr(obj, 'z'404# 获取属性'z',如果不存在,返回默认值404
      404

      也可以获得对象的方法:

      >>> hasattr(obj, 'power'# 有属性'power'吗?
      True
      >>> getattr(obj, 'power'# 获取属性'power'
      <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
      >>> fn = getattr(obj, 'power'# 获取属性'power'并赋值到变量fn
      >>> fn # fn指向obj.power
      <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
      >>> fn() # 调用fn()与调用obj.power()是一样的
      81

33、实例属性和类属性

  • class Demo:
        count=100#类变量 
        def __init__(self,name,age):
            self.__name=name
            self.__age=age


    d=Demo('zz',22)   
    d2=Demo('dd',11)
    d.count=222    # 给实例绑定count属性  类的count被隐藏了
    print(d2.count)  #100   因为实例并没有count属性,所以会继续查找class的count属性
    print(d.count)  #222
    print(Demo.count) #100
    del d.count  #删除d的实例属性  此时可以访问到类属性
    print(d.count)

    「在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性」

33、__slots__限制实例绑定任何属性和方法

  • 常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。但是,给一个实例绑定的方法,对另一个实例是不起作用的

  • 但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加nameage属性。

    为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

    class Student(object):
        __slots__ = ('name''age'# 用tuple定义允许绑定的属性名称
        
    >>> s.score = 99 # 绑定属性'score'
    Traceback (most recent call last):
      File "<stdin>", line 1in <module>
    AttributeError: 'Student' object has no attribute 'score'

    除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

  • 「len」

    • __len__
      如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。

      要让 len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。

      例如,我们写一个 Students 类,把名字传进去:

      class Students(object):
          def __init__(self, *args):
              self.names = args
          def __len__(self):
              return len(self.names)
      只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:

34、@property @xxx.setter

  • class Person(object):
        @property
        def birth(self):
            return self._birth

        @birth.setter
        def birth(self, value):
            self._birth = value

        @property
        def age(self):
            return 2015 - self._birth


    p = Person()
    p.birth = 100
    # p.age=11  #age为只读
    print(p.birth)
    print(p.age)

    把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值

35、多继承

  • Python允许使用多重继承

36、定制类

  • 「str」

    我们先定义一个Student类,打印一个实例:

    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...
    >>> print(Student('Michael'))
    <__main__.Student object at 0x109afb190>

    打印出一堆<__main__.Student object at 0x109afb190>,不好看。

    怎么才能打印得好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了:

    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...     def __str__(self):
    ...         return 'Student object (name: %s)' % self.name
    ...
    >>> print(Student('Michael'))
    Student object (name: Michael)

    这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。

怎么说呢,python 的特性太胶水了,不适合作为项目主力,所以它是生产力技能而不是业务技能。


猜你喜欢

数据地图:数据资产管理,到底管什么?

数据分析的三重境界,你在哪个阶段?

大厂晋升,你所需要知道的那些事(经验)


关于我们

更多精彩,请戳"阅读原文"到"数仓之路"查看

关注不迷路~ 各种干货、资源定期分享


学习小密圈 点击☞加群


分享,点赞,在看,安排一下?

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存