版权声明
这些笔记基于《Python for Data Analysis, 3rd Edition》一书的内容总结和理解。原书由 Wes McKinney 编写,出版商为 O’Reilly,ISBN 978-1-098-10403-0。版权声明如下:
MIT License
Original work Copyright (c) 2024 Wes McKinney
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
继续往下读,万万没想到,这本书竟然开始以及其简洁的语言介绍了python基础,作者真的是想一点一点的教会读者,唉,我哭了,很少有人能对我这么有耐心细腻,正好还能很好的复习之前学过的知识,很感动。
Python语言基础知识
Language Semantics
Python 语言被设计为易读、简单和明确,甚至被称为可执行的“伪代码”。
Indentation, not braces
Python 使用 4个空格或 1个制表符(tab) 来区分代码结构。:
表示下一个代码块缩进的开始,代码块结尾则是缩进的结束。
缩进示例
# 示例
for i in range(1,10):
a_str=''
for j in range(1,i+1):
a_str+=str(i)+'x'+str(j)+'='+str(i*j)
if j<i:
a_str+=' '
print(a_str)
Everything is an object
在刚开始自学 python学习之路☞3.变量 的时候,就有do佬 cureSky 说过:“一切皆对象”。现在我理解了,任何一个变量类型、函数、类、方法和模块都存在于 Python解释器中,是一个个的对象。每个对象都可以在Python解释器中找到对应的属性,并且以统一的方式处理和操作。
Comments
书籍之中只介绍了一种注释: #
Function and object method calls
通过 函数名(参数)
的方式调用函数,也可以通过 对象.方法(参数)
的方式调用对象内部的内容。对象方法的本质仍是函数。传参类型包括:位置传参、关键字传参和默认值传参。
Variables and argument passing
一旦一个变量赋值到另一个变量,这两个变量将引用同一个地址,这个机制在 Python 中被称为引用语义
。
代码例子
import copy
a = [1, 2, 3]
b = copy.copy(a) # 创建 a 的浅拷贝
print(id(a)) # 打印 a 的内存地址
print(id(b)) # 打印 b 的内存地址
# 输出结果会是不同的,证明 a 和 b 指向不同的对象
a = [1, 2, 3]
b = a # 现在 a 和 b 引用的是同一个列表对象
print(id(a)) # 打印 a 的内存地址
print(id(b)) # 打印 b 的内存地址
# 输出结果会是相同的,证明 a 和 b 指向同一个对象
了解这一点,对于一些事情的判断以及寻找错误发生的原因很有帮助。
书中举了一个生动的例子:对象赋值给变量,然后作为函数的传参。那么函数内会创建新的局部变量引用原始对象,如果修改了局部变量,也会影响到原始对象。然而如果函数内部的变量再进行二次赋值其他对象,情况还是这样吗?
根据原始对象特性的不同,结果也是不一样的。可变原始对象(如列表、字典和集合)和不可变原始对象(如数字、字符串和元组)有所不同。
可变原始对象
可变原始对象赋值变量作为传参,函数内修改会影响原始对象,函数内二次赋值则不会影响。
# 代码例子
a = [1,2,3] # 原始对象是可变对象 [1,2,3]
def append_list(b):# 测试传参函数
b.append('test')
print('b', b, id(b))
b = [4,5,6] # 二次赋值其他对象
print('b', b, id(b))
print('a', a, id(a))
append_list(a) # 调用函数确认这一事实
print('a', a, id(a)) # 看看原始对象是否改变?
不可变原始对象
不可变对象赋值变量作为传参,函数内修改不会影响原始对象,函数内二次赋值也不会影响。
a = 123 # 原始对象是不可变对象 123
def append_list(b):# 测试传参函数
b += 1
print('b', b, id(b))
b = 125 # 二次赋值其他对象
print('b', b, id(b))
print('a', a, id(a))
append_list(a) # 调用函数确认这一事实
print('a', a, id(a)) # 看看原始对象是否改变?
Dynamic references, strong types
变量的类型是不固定的,可以赋值引用任意类型的对象,这体现了动态引用的特性。
代码例子
a = 123
print(a, type(a)) # 输出 123 <class 'int'>
a = [1, 2, 3]
print(a, type(a)) # 输出 [1, 2, 3] <class 'list'>
Python 不会自动转换(隐式转换)变量类型。
代码例子
a = '5' + 10 # 会报错
这个时候需要手动转换(显式转换)变量类型。
代码例子
a = ord('5') + 10 # 通过 ord() 显式转换完成
print(a)
然而,真的乱不得吗?性格不同的对象就不能在一起吗?很显然,不仅仅是人生,Python 对象也有例外,这也就蕴含了无限生机。
代码例子
# 代码例子
a = '主' * 6 # 我愿称其为奇迹表达式
print(a, type(a))
书籍中也提供了一种函数 isinstance()
用来确认对象是否为特定类型的实例。
代码例子
a, b = 10, 10.9
print(isinstance(a, int)) # 判断对象特定类型
print(isinstance(b, (int, float))) # 判断对象是否符合类型范围
Attributes and methods
任何对象都具有属性和方法,可以通过 对象名.属性名
的方式调用。
代码例子
a = [1, 2, 3]
a.append('1')
a
属性和方法也可以通过 getattr(对象, "属性名")
获取访问方法。
代码例子
a = [1, 2, 3]
getattr(a, 'append')
Duck typing
“不论是黑猫白猫,能抓耗子就是好猫。” 我们关注的是对象的方法和行为,而不是类型。比如,大部分开发者都很喜欢循环具备迭代功能的对象。
代码例子
def isiterable(obj): # 代码已经做了修改
try:
iter(obj)
return obj, type(obj), True
except TypeError: # not iterable
return obj, type(obj), False
print(isiterable(123))
print(isiterable('123'))
print(isiterable(True))
print(isiterable([1, 2, 3]))
print(isiterable((1, 2, 3)))
print(isiterable({'a': 1, 'b': 2}))
print(isiterable({1, 2, 3}))
Imports
import
用于导入模块,模块只是一个包含 Python 代码的文件。
代码例子
# import 模块名
# import 模块名 as 别名
# from 模块名 import 属性名
# from 模块名 import *
※结合do佬 erhu 的提醒:注意不要使用 import *
,项目中的包名不要与标准库重名。
Binary operators and comparisons
运算符 ^
表示异或,is
和 is not
用于比较引用的对象是否相同。
代码练习
# ^
print(True ^ True)
print(False ^ True)
print(False ^ False)
# is
a = [1, 2, 3]
b = a
print(a is [1, 2, 3])
print(a is b)
# is not
print(a is not [1, 2, 3])
print(a is not b)
要注意,==
比较的是值,而 is
比较的是引用的对象。
代码例子
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)
print(a is b)
is
还可以判断变量是否为 None,None 只有一个实例。
代码例子
# 代码例子 None 只有一个唯一实例
a = None
b = None
print(a is None)
print(a == None)
print(a is b)
print(a == b)
Mutable and immutable objects
可变和不可变对象的讨论在之前传参的讨论中已提到过。书中建议尽量避免频繁修改可变对象。
Scalar Types
数字、字符串、布尔值和时间日期,这些单值通常被称为标量类型。包括 int
、float
、bool
、bytes
、str
和 None
。
Numeric types
数字类型包括 int
和 float
。
Strings
字符串是非常常用的对象类型,并且是不可改变的。\
是转义字符,如果希望转义字符正常打印可以使用 r'\'
。
代码例子
print('你\n好')
print(r'你\n好')
字符串对象有一个方法 str.format()
可以用于格式化字符串并替换到特定位置。
代码例子
a = '你好,我{0:d}岁了,我想买好吃的{1:s},哦天太贵了¥{2:.2f}'
a.format(30, '黄焖红烧肉饭', 28.789)
可以使用 f' '
代替 str.format()
方法。
代码例子
a = 30
b = '黄焖锅包肉鱼籽拌饭'
c = 89.639
print(f'我{a}岁了, 想吃{b},可是太贵了¥{c:.2f}')
表示多行字符串可以使用 ''' '''
或 """ """
。
代码例子
a = '''
你好世界!
嘿嘿世界!
啊,世界!
'''
b = """
生命的意义是什么?
我为什么存在?
我可不可以安乐死?
我好想消失,我一点也不快乐,唉
"""
print(a)
print(b)
字符串方法 str.count()
用于计数特定字符串的出现次数。
代码例子
print(a.count('\n'))
print(b.count('\n'))
字符串方法 str.replace()
可以替换特定字符串并返回新的字符串对象。
代码例子
print(a.replace('\n', '?\n'))
print(b.replace('我', ''))
字符串是 Unicode 字符序列,可以转换为 Lists 或 Tuples 等序列对象类型,从而具备序列对象的属性和方法。
类型转换
make_list = list(a)
make_tuple = tuple(b)
print(make_list)
print(make_tuple)
Bytes and Unicode
Bytes类型和Unicode类型是字符串类型。旧版本的Python通过字节处理字符串,新版本有了 Unicode 字符串类型,处理 ASCII 和非 ASCII 文本。
代码例子
a = '刘栋19950828'
print(a, type(a))
a = a.encode('gb2312') # 转为 gb2312 编码字节序列
print(a, type(a))
a = a.decode('gb2312') # 将 gb2312 字节序列还原为字符
print(a, type(a))
Booleans
布尔类型在自学 python学习之路☞5.运算符和表达式 时都练习过。
Type casting
类型强转在自学 python学习之路☞3.变量 时都练习过。
None
空类型在比较运算符 is
和 ==
之间的不同时提到过,None 只有一个实例。书中提到,None 也是常见的函数传参默认值,如 def function(a, b=None):
。
Dates and times
datetime
模块提供 datetime
、date
和 time
类型,datetime
是 date
和 time
对象的结合体。
datetime 实例化方法
from datetime import datetime, date, time # 导入模块
dt = datetime(2024, 11, 28, 18, 37, 10, 100000) # 年 月 日 时 分 秒 毫秒
print(f'{dt.year}-{dt.month}-{dt.day} {dt.hour}:{dt.minute}:{dt.second}.{dt.microsecond}') # 年-月-日 时:分:秒:毫秒
print(dt.date()) # 获取日期
print(dt.time()) # 获取时间
datetime 格式化方法
a = dt.strftime('%Y-%m-%d %H:%M:%S.%f')
print(a, type(a))
a = datetime.strptime('2024-11-28 18:37:10.010000', '%Y-%m-%d %H:%M:%S.%f')
print(a, type(a))
datetime 对象替换方法
datetime
对象是不可改变的类型,有一个方法 datetime_obj.replace()
可以替换时间日期任意一部分时间,并返回一个新的 datetime
对象,不会影响原始 datetime
对象。
print(a, type(a)) # 原始对象
b = dt.replace(year=2023, month=12, day=31, hour=12, minute=13, second=14, microsecond=15000) # 替换方法
print(b, type(b)) # 替换结果
print(a, type(a)) # 原始对象没变
datetime 对象之间的减运算
c = a - b # 两个 datetime 对象减法运算会产生 datetime.timedelta 对象
print(c, type(c))
d = b - c # datetime.timedelta 和 datetime 对象进行加减法运算会产生 datetime 对象
print(d, type(d))
Control Flow
流程控制中的分支结构和循环结构已经练习过,书中没有特别需要注意的内容。关键字 continue
、break
和 return
在分支结构和循环结构里十分有用。
if, elif, and else
在自学 python学习之路☞6.分支结构 if 时练习过。书中提到了一个链式比较 1 < 2 <= 3 < 4 == 4
,相当于 1 < 2 and 2 <= 3 and 3 < 4 and 4 == 4
。
代码
1 < 2 <= 3 < 4 == 4
for loops
在自学 python学习之路☞8.循环结构 for 时练习过。
while loops
在自学 python学习之路☞13.循环结构 while 时练习过。
Pass
pass
关键字是不执行任何操作,用于为未完成的代码占一个位置,执行时不会报错。
Range
range
内置函数可以生成等间距的数字序列,是个很常用的内置函数。