聪明办法学Python-2023-task02&03

发布时间 2023-11-26 00:01:20作者: Lyanv

参考资料:【数据类型和操作】 聪明办法学Python第二版_哔哩哔哩_bilibili

【变量与函数】 聪明办法学Python第二版_哔哩哔哩_bilibili

task02

常用内置类型 Built In Types

我们提前导入了 math 库,并创建了一个函数 f() (内容并不重要)

在本节中,我们将要见到这些基本类型:

  • 整数 Integer(int)(离散,分类:返回一个类属)
  • 浮点数 Float [默认双精度浮点型]
  • 布尔值 Boolean(bool) [True或False]
  • 类型 Type(是的,“类型”也是种类型!)

严格的来说,Type 是一种 对象,Python 是一门“面向对象友好”的语言

print(type(2)) #用type可以查看对象类型
<class 'int'>
print(type(2.2))
<class 'float'>
print(type(2 < 2.2))
<class 'bool'>
print(type(type(42))) # 类型的对象
<class 'type'>

在今后的内容中,我们将会见到更多类型:

  • 字符串 String(str)
  • 列表 List
  • 元组 Tuple
  • 集合 Set
  • 字典 Dictionary(dict,或者你可以叫它 映射 map
  • 复数 Complex Number(complex) {Python可以用类似 1+2j的形式表示}
  • 函数 Function
  • 模块 Module

strlisttuplesetdict 将尝试用 数组 Array 的方式讲授

import math

def f():
    pass

print(type("2.2"))       # str (string or text)
print(type([1,2,3]))     # list
print(type((1,2,3)))     # tuple
print(type({1,2}))       # set
print(type({1:42}))      # dict (dictionary or map)
print(type(2+3j))        # complex  (complex number)
print(type(f))           # function 函数
print(type(math))        # module 模块
<class 'str'>
<class 'list'>
<class 'tuple'>
<class 'set'>
<class 'dict'>
<class 'complex'>
<class 'function'>
<class 'module'>

常用内置常数 Built In Constants

常数区别于变量(将在下节课讲授),常数的值是固定的、不可改变的

Python 内置了一些常量

  • True,用于表示 布尔
  • False,用于表示 布尔
  • None,代表 ,用于空值 (可以置空变量)

math 库中的一些数学常量

  • pi,数学常数 π = 3.141592...,精确到可用精度
  • e,数学常数 e = 2.718281...,精确到可用精度
  • tau,数学常数 τ = 6.283185...,精确到可用精度(其实它不常用)
  • inf,浮点正无穷大,等价于 float('inf'),负无穷大使用 -math.inf
print(True)  # bool型和空型
print(False)
print(None)
True
False
None
print(math.pi) # tau是pi的俩倍
print(math.e)
print(math.tau)
print(math.inf)  # 正无穷和负无穷
print(-math.inf)
3.141592653589793
2.718281828459045
6.283185307179586
inf
-inf

常用内置运算符 Built In Operators

  • 算术:+, -, *, @(矩阵乘法), /(浮点除), //(整除), **, %(模运算), - (一元算符), + (一元算符)
  • 关系:<, <=, >=, >, ==(判断是否相等,一个等于号是赋值), !=
  • 赋值: +=, -=, *=, /=, //=, **=, %=(简写依然适用)
  • 逻辑:and, or, not (与,或,非)

我们暂时跳过按位运算符

整除 Integer Division (//)

这个知识点可能会在作业中发挥很大的作用,所以请多花些时间来理解它的运作方式

/` 指的是**浮点数**除法,它的结果是一个浮点数,例如 `2/1` 的结果是 `2.0

// 指的是整除除法,它的计算结果是整数,舍弃余数

/浮点数 除法操作符 (返回的是浮点数)

print(" 5/3  =", (5/3))
 5/3  = 1.6666666666666667

// 代表 整除:

print(" 5//3 =", ( 5//3))
print(" 2//3 =", ( 2//3))
print("-1//3 =", (-1//3))
print("-4//3 =", (-4//3))
 5//3 = 1
 2//3 = 0
-1//3 = -1 # 往后退一位了
-4//3 = -2

模运算或余数运算符 (%)

这个知识点可能会在作业中发挥很大的作用,所以请多花些时间来理解它的运作方式

% 代表模运算(取余),结果为商的余数

例如:5 整除 2 的结果是 2余数1,则 5 % 2 的结果为 1

print(" 6%3 =", ( 6%3))
print(" 5%3 =", ( 5%3)) 
print(" 2%3 =", ( 2%3))
print(" 0%3 =", ( 0%3))
print("-4%3 =", (-4%3))
print(" 3%0 =", ( 3%0))
 6%3 = 0
 5%3 = 2
 2%3 = 2
 0%3 = 0
-4%3 = 2

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In [40], line 6
      4 print(" 0%3 =", ( 0%3))
      5 print("-4%3 =", (-4%3))
----> 6 print(" 3%0 =", ( 3%0))

ZeroDivisionError: integer division or modulo by zero

# 这里不能做被除数

模运算

a mod b⟺a−(a∣b)×b

def mod(a, b):
  return a - (a//b)*b

而这个函数和我们自己取余的结果没有区别

print(41%14 == mod(41,14))
print(14%41 == mod(14,41))
print(-32%9 == mod(-32,9))
print(32%-9 == mod(32,-9))
True
True
True
True

所以%和我们的公式是等价的

除数这里有两种说法,可能会有歧义↓

补充资料:注意 %math.fmod() 的区别,详见:Modulo operation

类型影响语义 Types Affect Semantics

运算符的运作方式会受到运算数据的类型的影响

print(3 * 2)
print(3 * "p2s")
print(3 + 2)
print("Data" + "whale")
print(3 + "p2s")
6
p2sp2sp2s
5
Datawhale
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [41], line 5
      3 print(3 + 2)
      4 print("Data" + "whale")
----> 5 print(3 + "p2s")

TypeError: unsupported operand type(s) for +: 'int' and 'str'

# 报错了,整数不能和字符串直接相加(加了‘’就是字符串类型)

运算符优先级 Operator Order

优先顺序与结合律 Precedence and Associativity

见Python参考文档:

6. 表达式 — Python 3.9.17 文档

:=海象表达式

优先顺序 Precedence

print(2+3*4)  # 14(不是 20) 
print(5+4%3)  # 6(不是 0)
print(2**3*4) # 32(不是 4096)
14      # 正常算数优先级
6
32

结合律 Associativity

在加减乘除里是从左往右算

在次级运算里是从右往左算

print(5-4-3)   # -2(不是 4)
print(4**3**2) # 262144(不是 4096) # 这里是从后往前算

浮点数误差

print(0.1 + 0.1 == 0.2)        # True
print(0.1 + 0.1 + 0.1 == 0.3)  # False!
print(0.1 + 0.1 + 0.1)         # Why?
print((0.1 + 0.1 + 0.1) - 0.3) # 特别小,但不是 0
True
False
0.30000000000000004
5.551115123125783e-17

原因解释:
浮点数(小数)在计算机中实际是以二进制存储的,并不精确。
比如0.1是十进制,转换为二进制后就是一个无限循环的数:
0.00011001100110011001100110011001100110011001100110011001100
python默认的浮点类型只能保证小数点后8位的精确度。(因而产生一个极小的误差↓)
python是以双精度(64bit)来保存浮点数的,后面多余的会被砍掉,所以在电脑上实际保存的已经小于0.1的值了,后面拿来参与运算就产生了误差。

解决办法:
使用decimal库
from decimal import *
a = Decimal(‘4.2’)
b = Decimal(‘2.1’)
c = a * b

短路求值 Short-Circuit Evaluation

电路里比较基本的只是带你

def yes():
    return True

def no():
    return False

def crash():
    return 1/0 # 会崩溃!
print(no() and crash()) # 成功运行! 与运算 ## 因为第一个为假,python程序就不会判断后面的了,一个为假即“与”运算结果为假
print(crash() and no()) # 崩溃了!
print (yes() and crash()) # 因为上一行崩溃了,所以这行不会被运行,就是运行也会因为短路求值崩溃
# 错误信息
False
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In [47], line 3
      1 print(no() and crash()) # 成功运行!
----> 3 print (yes() and crash())

Cell In [45], line 8, in crash()
      7 def crash():
----> 8     return 1/0

ZeroDivisionError: division by zero
 # 因为上一行崩溃了,所以第三行不会被运行,就是运行也会因为短路求值崩溃

我们换成 or,再来试试

print(yes() or crash()) # 成功运行 ## 一个真就不会判断后面的了,就是直接输出True
# print(crash() or yes()) # 崩溃了
print(no() or crash())  # 因为上一行崩溃了,所以这行不会被运行,就是运行也会因为短路求值崩溃 ## 如果这行运行了,第一个为假。还得判断第二个(因为是有一个为真就是真)
True
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In [50], line 3
      1 print(yes() or crash()) # 成功运行
      2 # print(crash() or yes()) # 崩溃了
----> 3 print(no() or crash())

Cell In [45], line 8, in crash()
      7 def crash():
----> 8     return 1/0

ZeroDivisionError: division by zero

再来个例子,我们也先定义些函数

def isPositive(n):
    result = (n > 0)
    print(n, "是不是正数?", result)
    return result

def isEven(n):
    result = (n % 2 == 0) # 能被整除
    print(n, "是不是偶数?", result)
    return result
print(isEven(-4) and isPositive(-4)) # 调用了两个函数
-4 是不是偶数? True
-4 是不是正数? False
False
print(isEven(-3) and isPositive(-3)) # 只调用了一个函数
-3 是不是偶数? False #这里只会输出左边(因为一方False全都False)
False

type() vs isinstance()

print(type("p2s") == str)
print(isinstance("p2s", str))
True
True

任务:编写代码,判断 x 是不是数字

def isNumber(x):
    return ((type(x) == int) or # 想法是:要么整数要么浮点数
            (type(x) == float)) 

你能确保它能够判断所有数字吗? 不能,因为少了复数

print(isNumber(1), isNumber(1.1), isNumber(1+2j), isNumber("p2s"))
True True False False
  • isinstance()type() 更具有 稳健性(Robustness) (变成判断示例)
  • 这种做法更加符合 面向对象编程继承(inheritance) 的思想 (覆盖范围更广)
import numbers # 这是Python里面所有关于数字的库
def isNumber(x):
    return isinstance(x, numbers.Number) # 可以应对任何类型的数字 ## 覆盖了所有数字情况
print(isNumber(1), isNumber(1.1), isNumber(1+2j), isNumber("p2s"))
True True True False

task02总结

  • Python 的类型系统很丰富,可以使用 type() 查看对应的类型
  • 常数类型的值是不可修改的给常数的值赋值会报错
  • 除法操作默认是浮点数除法,整除操作需要使用 //
  • 运算符之间有运算优先级,运算符作用于不同对象之间的效果是不同的
  • 在进行逻辑判断时,会使用短路求值

task03

变量 Variables

A variable is a named value that references or stores a piece of data.

  • 变量是一个名字,它所指代的是一段数据
  • 使用 = 来对这段数据的区域进行赋值
  • 可以把变量理解成一个指针
x = 5  # x指向常数5,如果有y = x,即y也指向常数5(所在的内存地址[老指针了])
print(x)
5
print(x*2)
10
  • 新的值会覆盖掉旧的值
  • 新值的数据类型不必与旧值相同
y = 10
print(y - 2)
8
y = True # 拿True覆盖了,数据类型不必相同(在python中可以赋其他的数据类型)
print(y)
True

变量命名规则:

  • 必须以字母或下划线(_)开头
  • 命名可由字母、数字和下划线组成
  • 大小写敏感
  • 尽量避免使用保留字命名

例:

numberOfRabbits = 40
courseIs15112 = True
99problems = 0 # 会崩溃!因为变量名以数字开头
  Cell In [13], line 1
    99problems = 0 # 会崩溃!因为变量名以数字开头
      ^
SyntaxError: invalid syntax

保留字

import keyword
print(keyword.kwlist)
['False',
 'None',
 'True',
 '__peg_parser__',
 'and',
 'as',
 'assert',
 'async',
 'await',
 'break',
 'class',
 'continue',
 'def',
 'del',
 'elif',
 'else',
 'except',
 'finally',
 'for',
 'from',
 'global',
 'if',
 'import',
 'in',
 'is',
 'lambda',
 'nonlocal',
 'not',
 'or',
 'pass',
 'raise',
 'return',
 'try',
 'while',
 'with',
 'yield']

更新变量

x = 5
x += 2 # 等价于 x = x + 2
print(x) # 7
7
# 换成其他运算符也是同理
y = 350
y //= 10
print(y) # 35
35

多变量赋值

a = b = c = 2
print(f"a={a}, b={b}, c={c}")
a=2, b=2, c=2
a, b, c = 1, 2, 6
print(f"a={a}, b={b}, c={c}")
a=1, b=2, c=6

函数 Functions

A function is a procedure (a sequence of statements) stored under a name that can be used repeatedly by calling the name.

  • 函数是一个名字,代表一串代码序列(流程、过程)
  • 函数由两个部分组成:headerbody
    • header 用于定义函数接口(函数 名称参数
    • body 包含函数所需要执行的操作

header 用于定义函数的名称参数

  • 当函数被调用时,参数将会作为变量被提供给函数的 body 部分
  • 可以提供多个参数(用逗号 , 分隔),也可以不提供参数(0 个)
  • header 以冒号:结尾,代表后面会跟着 body 部分

函数的 header 的写法:

def functionName(parameters): # define 类似f(x,y)
    pass # 函数的 body 部分,这里使用 pass 代替 (临时组建函数且不被调用)
# 有缩进块 代码风格

body

body 包含函数执行的语句(statement

  • 语句需要缩进(由 Code Style Guide 决定)
  • 当语句不再缩进,函数部分结束
  • 一般会使用 return 语句,来让函数返回其结果,但不是必须的

类似于用一个 = 来对多个变量赋值,函数的返回结果也可以不止一个(用逗号 , 分隔)

下面我们用一个例子来解释函数的细节

def double(x):
    print("我在一个名叫 “double” 函数里!")
    return 2 * x
  • 我们使用函数名来调用函数
  • 函数名后紧跟一对括号
  • 括号中是我们设定的参数的,一个不多,一个不少(这很重要)
  • 函数会返回设定的 return 语句的

调用示例函数 double() 会返回一个值(2 * x

print(double(2)) # 会输出 4
我在一个名叫 “double” 函数里!
4
print(double(5)) # 会输出 10
我在一个名叫 “double” 函数里!
10
print(double(1) + 3) # 会输出 5 ## 先计算double(1)的值,再给这个值加上3打印
我在一个名叫 “double” 函数里!
5

函数可以有任意多个参数,也可以一个都没有

# 三个参数
def f(x, y, z):
    return x + y + z

print(f(1, 3, 2)) # 返回 6
6
# 无参数
def g():
    return 42

print(g()) # 返回 42
42

可是如果参数数目没有匹配的话……Oops!

print(g(2)) # 崩溃!
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [29], line 1
----> 1 print(g(2))

TypeError: g() takes 0 positional arguments but 1 was given
# 无参数报错
print(f(1, 2)) # 也会崩溃
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [30], line 1
----> 1 print(f(1, 2))

TypeError: f() missing 1 required positional argument: 'z'
# 参数少了,可以用形参

多返回值

def Multi_Return_Values():
    return 9, 2, 8
a, b, c = Multi_Return_Values() # 相当于返回一个list然后分别存再abc
print(f"a={a}, b={b}, c={c}")
a=9, b=2, c=8

语句与表达式 Statements and Expressions

An expression is a data value or an operation that evaluates to a value.

对于表达式

  • 它本身是
  • 它的计算结果是值

Statements, by contrast, do not evaluate to a value, and we can't print them. Usually they perform some action, though.

对于语句

  • 它不是值
  • 它不能打印
  • 但它能执行一些操作

表达式的一些例子:

# 以下在python的cmd中执行
4
4
"Hello World"
'Hello World'
7 + 2
9
True or False
True
(2 < 3) and (9 > 0)
True

Python 只能 print 值和表达式,如果你能用 print() 输出它,那它就是表达式

print((2 < 3) and (9 > 0))
True

语句的一些例子

def f(x):
    return 5*x
x = 5 + 4
if 10 > 5:
    y = 5 + 3

内置函数 Built In Functions

就是 Python 自己带的函数啦?

类型转换

print(bool(0))   # 转换为布尔类型(True or False) ## 1就输出为True(严格来讲除了空或者0都是真)
False
print(float(42)) # 转换为浮点数
42.0
print(int(2.8))  # 转换为一个整数(舍弃小数点)
2

一些基本数学函数

但是它们不在 math 库中

print(abs(-5))   # 绝对值
5
print(max(2,3))  # 返回最大值
3
print(min(2,3))  # 返回最小值
2
print(pow(2,10))  # 次方运算,等价于 2**10
1024
print(round(2.354, 2)) # 取最近的一个整数(并不完全是四舍五入,二进制精度丢失) ## 保留两位小数,用round(四舍五入表示的是二进制小数(或者用decimal库来避免))
2.35

变量作用域 Variable Scope

  • 每个变量都有属于自己的作用范围
  • 超出作用范围后,变量不可见

我们设定一个函数 f(x), 它的内部有 xy 两个变量

记得一定要重启 Jupyter Kernel!

def f(x): # 形参
    print("x:", x)
    y = 5 # 内部定义的
    print("y:", y)
    return x + y
print(f(4))
x: 4
y: 5
9
# 如果在外面放一个x = 5,打印出来还是全局变量x = 5的值 (上面的仅是形参)
print(x) # crash! ## x为局部变量
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [3], line 1
----> 1 print(x)

NameError: name 'x' is not defined
print(y) # crash!
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [4], line 1
----> 1 print(y)

NameError: name 'y' is not defined
# y未在全局变量

函数内的变量具有局部作用域,它只存在于函数内部,与其他函数中的同名变量无关

def f(x):
    print("In f, x =", x)
    x += 5
    return x
# return回去了传进来的参数会值会变

def g(x):
    y = f(x*2)
    print("In g, x =", x)
    z = f(x*3)
    print("In g, x =", x)
    return y + z

print(g(2))
In f, x = 4
In g, x = 2
In f, x = 6
In g, x = 2
20

Python Tutor code visualizer: Visualize code in Python, JavaScript, C, C++, and Java

这个网站能一步一步看代码运行逻辑(如何运行的这段代码)

def f(x):
    print("In f, x =", x)
    x += 7
    return round(x / 3)

def g(x):
    x *= 10
    return 2 * f(x)

def h(x):
    x += 3
    return f(x+4) + g(x)

print(h(f(1)))
In f, x = 1
In f, x = 10
In f, x = 60
50

在函数外部定义变量时,变量具有全局作用域,在任何地方都可以使用

我们应该尽量避免使用全局变量,但是在非常少的一些场合你会需要用到它

g = 100

def f(x):
    return x + g

print(f(5)) # 105
print(f(6)) # 106
print(g)    # 100
105
106
100
g = 100

def f(x):
    # 如果我们想要修改 g 的值,我们必须声明它是全局变量
    # 否则 Python 会假设它是局部变量
    global g
    g += 1
    return x + g

print(f(5)) # 106 ## 这里修改了一次g
print(f(6)) # 108 ## 这里修改了第二次g
print(g)    # 102
106
108
102

返回语句 Return Statements

def isPositive(x):
    return (x > 0) # 条件判断返回布尔值 ## 本身是值或者表达式结果是值
print(isPositive(5))  # True
True
print(isPositive(-5)) # False
False
print(isPositive(0))  # False
False

一旦返回,函数立即结束!

def isPositive(x):
    print("Hello!")   # 会运行
    return (x > 0)
    print("Goodbye!") # 不会运行

print(isPositive(5))  # 输出 “Hello!” 然后返回 True
Hello!
True

没有返回语句的时候,函数会返回 None

def f(x):
    x + 42

print(f(5)) # None
None
##--------------------------------------------------------------------------------------------------------##
def f(x):
    result = x + 42

print(f(5)) # None
None

print()return 是初学者比较容易出现的错误

def cubed(x):
    print(x**3)  # 这里的操作不太合适
cubed(2)  # 但是似乎看起来也正常运行了
8
print(cubed(3))  # 应该也能有效(但是返回 None,太怪了)
27
None
## 因为这里采用了嵌套方式,相当于 print(print(3**3))
## 而cubed(3)中的print里的语句已经将参数改变.然后第二次嵌套相当于把这个改变的x也给输出,然后再输出print类型
print(2*cubed(4)) # Error!
64
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [41], line 1
----> 1 print(2*cubed(4))

TypeError: unsupported operand type(s) for *: 'int' and 'NoneType'
# 

正确写法:

def cubed(x):
    return (x**3) # 这样做更好
cubed(2)  #似乎输出被忽略了,为什么? ## 没打印到屏幕上
8 # 没打印出来
print(cubed(3))  # 有效了!
27
print(2*cubed(4))  # 也是有效的!
128 # 因为这里cubed(4)是一个值,可以进行和差积商

函数组合 Function Composition

对于嵌套的函数而言,应该最先运行最内层的函数

def f(w):
    return 10*w

def g(x, y):
    return f(3*x) + y  #在我们返回它之前,我们必须先执行 f(3*x)

def h(z):
    return f(g(z, f(z+1)))  # 最内部的 f(z+1) 必须先执行

print(h(1)) # 你一定得“亲眼看看”
500
## 相当于递归

Helper Functions

编写函数是用来解决问题的

我们还可以编写函数来存储那些经常被用到的一系列操作

简化程序,多模块执行

这种函数就叫做 Helper Function

def onesDigit(n):
    return n%10

def largerOnesDigit(x, y):
    return max(onesDigit(x), onesDigit(y))

print(largerOnesDigit(134, 672)) # 4
print(largerOnesDigit(132, 674)) # 依然是 4
4
4

## 相当于一个工具做一个部分

Don’t be the person who “never quite understood” something like recursion.

—— Teach Yourself Computer Science

补充资料:

永远不要做一个不理解递归的程序员[很重要的一个思想]

task03总结

  • 变量只是个标签,物理设备上有啥才是重点
  • 函数定义:def、header、body、缩进、return
  • 函数是有作用域的,类似双层玻璃,里面可以看见外面,外面不能看见里面
  • Helper Function 有时候会很有用
  • 一定要亲眼看你的代码是怎么跑起来的