Python学习

一、基础篇

01 学习方法

  • 大厦之基,勤加练习
  • 代码规范,必不可少

02 jupyter notebook

到底什么是 Jupyter Notebook?按照 Jupyter 创始人 Fernando Pérez 的说法,他最初的梦想是做一个综合 Ju (Julia)、Py (Python)和 R 三种科学运算语言的计算工具平台,所以将其命名为 Ju-Py-te-R。发展到现在,Jupyter 已经成为一个几乎支持所有语言,能够把软件代码、计算输出、解释文档、多媒体资源整合在一起的多功能科学运算平台。

  • Jupyter 的优点
  1. 整合所有的资源
  2. 交互性编程体验
  3. 零成本重现结果

03 列表和元组

  • 列表(list)和元组(tuple),都是一个可以放置任意数据类型的有序集合
l = [1, 2, 'hello', 'world'] # 列表中同时含有 int 和 string 类型的元素
l
[1, 2, 'hello', 'world']
 
tup = ('jason', 22) # 元组中同时含有 int 和 string 类型的元素
tup
('jason', 22)
  • 列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素(mutable)。

  • 而元组是静态的,长度大小固定,无法增加删减或者改变(immutable)。

l = [1, 2, 3, 4]
l[3] = 40 # 和很多语言类似,python 中索引同样从 0 开始,l[3] 表示访问列表的第四个元素
l
[1, 2, 3, 40]
 
tup = (1, 2, 3, 4)
tup[3] = 40
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

如果你想对已有的元组做任何"改变",该怎么办呢?那就只能重新开辟一块内存,创建新的元组了。

比如下面的例子,我们想增加一个元素 5 给元组,实际上就是创建了一个新的元组,然后把原来两个元组的值依次填充进去。

而对于列表来说,由于其是动态的,我们只需简单地在列表末尾,加入对应元素就可以了。如下操作后,会修改原来列表中的元素,而不会创建新的列表。

tup = (1, 2, 3, 4)
new_tup = tup + (5, ) # 创建新的元组 new_tup,并依次填充原元组的值
new _tup
(1, 2, 3, 4, 5)
 
l = [1, 2, 3, 4]
l.append(5) # 添加元素 5 到原列表的末尾
l
[1, 2, 3, 4, 5]
  • 和其他语言不同,Python 中的列表和元组都支持负数索引,-1 表示最后一个元素,-2 表示倒数第二个元素,以此类推。
l = [1, 2, 3, 4]
l[-1]
4
 
tup = (1, 2, 3, 4)
tup[-1]
4
  • 除了基本的初始化,索引外,列表和元组都支持切片操作,区间左闭右开
l = [1, 2, 3, 4]
l[1:3] # 返回列表中索引从 1 到 2 的子列表
[2, 3]
 
tup = (1, 2, 3, 4)
tup[1:3] # 返回元组中索引从 1 到 2 的子元组
(2, 3) 
  • 列表和元组都可以随意嵌套
l = [[1, 2, 3], [4, 5]] # 列表的每一个元素也是一个列表
 
tup = ((1, 2, 3), (4, 5, 6)) # 元组的每一个元素也是一元组
  • 两者也可以通过 list() 和 tuple() 函数相互转换
list((1, 2, 3))
[1, 2, 3]
 
tuple([1, 2, 3])
(1, 2, 3)
  • 列表和元组的内置函数
l = [3, 2, 3, 7, 8, 1]
l.count(3) 
2
l.index(7)
3
l.reverse()
l
[1, 8, 7, 3, 2, 3]
l.sort()
l
[1, 2, 3, 3, 7, 8]
 
tup = (3, 2, 3, 7, 8, 1)
tup.count(3)
2
tup.index(7)
3
list(reversed(tup))
[1, 8, 7, 3, 2, 3]
sorted(tup)
[1, 2, 3, 3, 7, 8]
  • count(item) 表示统计列表 / 元组中 item 出现的次数。

  • index(item) 表示返回列表 / 元组中 item 第一次出现的索引。

  • list.reverse() 和 list.sort() 分别表示原地倒转列表和排序(注意,元组没有内置的这两个函数)。

  • reversed()sorted() 同样表示对列表 / 元组进行倒转和排序,但是会返回一个倒转后或者排好序的新的列表 / 元组。

  • 列表和元组存储方式的差异 前面说了,列表和元组最重要的区别就是,列表是动态的、可变的,而元组是静态的、不可变的。这样的差异,势必会影响两者存储方式:

l = [1, 2, 3]
l.__sizeof__()
64
tup = (1, 2, 3)
tup.__sizeof__()
48

对列表和元组,我们放置了相同的元素,但是元组的存储空间,却比列表要少 16 字节。这是为什么呢?

事实上,由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述例子中,对于 int 型,8 字节)。另外,由于列表可变,所以需要额外存储已经分配的长度大小(8 字节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间。

l = []
l.__sizeof__() // 空列表的存储空间为 40 字节
40
l.append(1)
l.__sizeof__() 
72 // 加入了元素 1 之后,列表为其分配了可以存储 4 个元素的空间 (72 - 40)/8 = 4
l.append(2) 
l.__sizeof__()
72 // 由于之前分配了空间,所以加入元素 2,列表空间不变
l.append(3)
l.__sizeof__() 
72 // 同上
l.append(4)
l.__sizeof__() 
72 // 同上
l.append(5)
l.__sizeof__() 
104 // 加入元素 5 之后,列表的空间不足,所以又额外分配了可以存储 4 个元素的空间

元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表。

元组可以对静态数据做一些资源缓存(resource caching)。

元组初始化速度更快,索引速度与列表相差不大,增加、删减或者改变元素,那么列表显然更优。

# 创建空列表
# option A
empty_list = list()
 
# option B
empty_list = []
  • []比list()更快,因为调用list函数有一定的开销,Python的function call会创建stack,并且进行一系列参数检查的操作,比较expensive,而[]却没有.
  • []是一个内置的C函数,可以直接被调用

03 字典和集合

字典是一系列由键(key)和值(value)配对组成的元素的集合,在 Python3.7+,字典被确定为有序(注意:在 3.6 中,字典有序是一个 implementation detail,在 3.7 才正式成为语言特性,因此 3.6 中无法 100% 确保其有序性),而 3.6 之前是无序的,其长度大小可变,元素可以任意地删减和改变。

字典的性能更优,特别是对于查找、添加和删除操作,字典都能在常数时间复杂度内完成。

而集合和字典基本相同,唯一的区别,就是集合没有键和值的配对,是一系列无序的、唯一的元素组合。

d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}
d2 = dict({'name': 'jason', 'age': 20, 'gender': 'male'})
d3 = dict([('name', 'jason'), ('age', 20), ('gender', 'male')])
d4 = dict(name='jason', age=20, gender='male') 
d1 == d2 == d3 ==d4
True
 
s1 = {1, 2, 3}
s2 = set([1, 2, 3])
s1 == s2
True

Python 中字典和集合,无论是键还是值,都可以是混合类型。比如下面这个例子,我创建了一个元素为1,'hello',5.0的集合:s = {1, 'hello', 5.0}

字典访问可以直接索引键,如果不存在,就会抛出异常:

d = {'name': 'jason', 'age': 20}
d['name']
'jason'
d['location']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'location'

也可以使用 get(key, default) 函数来进行索引。如果键不存在,调用 get() 函数可以返回一个默认值。比如下面这个示例,返回了'null'。

d = {'name': 'jason', 'age': 20}
d.get('name')
'jason'
d.get('location', 'null')
'null'

集合并不支持索引操作,因为集合本质上是一个哈希表,和列表不一样。所以,下面这样的操作是错误的,Python 会抛出异常:

s = {1, 2, 3}
1 in s
True
10 in s
False
 
d = {'name': 'jason', 'age': 20}
'name' in d
True
'location' in d
False

字典和集合也同样支持增加、删除、更新等操作。

d = {'name': 'jason', 'age': 20}
d['gender'] = 'male' # 增加元素对'gender': 'male'
d['dob'] = '1999-02-01' # 增加元素对'dob': '1999-02-01'
d
{'name': 'jason', 'age': 20, 'gender': 'male', 'dob': '1999-02-01'}
d['dob'] = '1998-01-01' # 更新键'dob'对应的值 
d.pop('dob') # 删除键为'dob'的元素对
'1998-01-01'
d
{'name': 'jason', 'age': 20, 'gender': 'male'}
 
s = {1, 2, 3}
s.add(4) # 增加元素 4 到集合
s
{1, 2, 3, 4}
s.remove(4) # 从集合中删除元素 4
s
{1, 2, 3}

集合的 pop() 操作是删除集合中最后一个元素,可是集合本身是无序的,你无法知道会删除哪个元素,因此这个操作得谨慎使用。

我们需要对字典或集合进行排序,对于字典,我们通常会根据键或值,进行升序或降序排序:

d = {'b': 1, 'a': 2, 'c': 10}
d_sorted_by_key = sorted(d.items(), key=lambda x: x[0]) # 根据字典键的升序排序
d_sorted_by_value = sorted(d.items(), key=lambda x: x[1]) # 根据字典值的升序排序
d_sorted_by_key
[('a', 2), ('b', 1), ('c', 10)]
d_sorted_by_value
[('b', 1), ('a', 2), ('c', 10)]

这里返回了一个列表。列表中的每个元素,是由原字典的键和值组成的元组。

而对于集合,其排序和前面讲过的列表、元组很类似,直接调用 sorted(set) 即可,结果会返回一个排好序的列表。

s = {3, 4, 2, 1}
sorted(s) # 对集合的元素进行升序排序
[1, 2, 3, 4]

集合这个数据结构,由于集合是高度优化的哈希表,里面元素不能重复,并且其添加和查找操作只需 O(1) 的复杂度

  • 字典和集合的内部结构都是一张哈希表。
  • 对于字典而言,这张表存储了哈希值(hash)、键和值这 3 个元素。
  • 而对集合来说,区别就是哈希表内没有键和值的配对,只有单一的元素了

为了提高存储空间的利用率,现在的哈希表除了字典本身的结构,会把索引和哈希值、键、值单独分开,也就是下面这样新的结构:

每次向字典或集合插入一个元素时,Python 会首先计算键的哈希值(hash(key)),再和 mask = PyDicMinSize - 1 做与操作,计算这个元素应该插入哈希表的位置 index = hash(key) & mask。如果哈希表中此位置是空的,那么这个元素就会被插入其中。

  • 而如果此位置已被占用,Python 便会比较两个元素的哈希值和键是否相等。

  • 若两者都相等,则表明这个元素已经存在,如果值不同,则更新值。

若两者中有一个不相等,这种情况我们通常称为哈希冲突(hash collision),意思是两个元素的键不相等,但是哈希值相等。这种情况下,Python 便会继续寻找表中空余的位置,直到找到位置为止。

  • 查找操作 和前面的插入操作类似,Python 会根据哈希值,找到其应该处于的位置;然后,比较哈希表这个位置中元素的哈希值和键,与需要查找的元素是否相等。如果相等,则直接返回;如果不等,则继续查找,直到找到空位或者抛出异常为止。

  • 删除操作 对于删除操作,Python 会暂时对这个位置的元素,赋于一个特殊的值,等到重新调整哈希表的大小时,再将其删除。

不难理解,哈希冲突的发生,往往会降低字典和集合操作的速度。因此,为了保证其高效性,字典和集合内的哈希表,通常会保证其至少留有 1/3 的剩余空间。随着元素的不停插入,当剩余空间小于 1/3 时,Python 会重新获取更大的内存空间,扩充哈希表。不过,这种情况下,表内所有的元素位置都会被重新排放。

虽然哈希冲突和哈希表大小的调整,都会导致速度减缓,但是这种情况发生的次数极少。所以,平均情况下,这仍能保证插入、查找和删除的时间复杂度为 O(1)。

05 字符串

单引号('')双引号("")或者三引号('''''')或("""""")中,方便内嵌带引号的字符串

字符串内部支持转义字符。

字符串同样支持索引、切片和遍历等操作。

name = 'jason'
name[0]
'j'
name[1:3]
'as'

遍历字符串

for char in name:
	print(char)
j
a
s
o
n

python中字符串是不可变的也即以下操作是错误的:s = 'hello'; s[0] = 'H'

python中字符串的改变只能通过创建新的字符串完成。

s = 'H' + s[1:]						# 拼接
s = s.replace('h', 'H');			# 函数

字符串可以使用+=进行拼接

s = ''
for n in range(0, 1000):			# 这里范围是[0,1000)
	s += str(n)

append相当于C++中的push_back。str.join(iterable)可以实现按照指定格式链接字符串:iterable中的元素之间使用str进行分隔。

l = []
for n in range(0, 10):
    l.append(str(n))
l = ' '.join(l)			# '0 1 2 3 4 5 6 7 8 9'

字符串分割splice函数

path = 'hive://ads/training_table'
namespace = path.split('//')[1].split('/')[0]			# 'ads'
table = path.split('//')[1].split('/')[1]				# 'training_table'

其他字符串函数

  • string.strip(str) : 表示去掉首尾的str字符串
  • string.lstrip(str) : 表示只去掉开头的str字符串
  • string.rstrip(str) : 表示字去掉末尾的str字符串 去除首尾空格:
s = ' my name is jason '
s.strip()						# 这里会生成一个新的字符串'my name is jason',源字符串s不变

格式化输出str.format()

print('id:{}, name:{}'.format(id, name))
print('id:%s, name:%s'%(id, name))

06 输入与输出

input()用于从键盘读取字符串,阻塞。

name = input('your name:')
gender = input('you are a boy ?(y/n)')

type(var)可以把var变量的值得类型显示出来。

强制转换int(),float()。python没有int型得最大限制。

  • 文本处理 open()函数拿到文件的指针,第一个参数为文件路径,第二个参数是权限:rwrwa(追加append)。

test = fin.read()打开的指针fin对应的文件使用read函数赋值给test

  • JSON序列化 json.dumps()接受基本数据类型,将其序列化为string类型。

json.loads()接受一个合法的字符串,将其反序列化为基本数据类型

import json
params = {
    'symbol':'123456',
    'type':'limit',
    'price':123.4,
    'amount':23
}
params_str = json.dumps(params)   
#'{"symbol": "123456", "type": "limit", "price": 123.4, "amount": 23}'
original_params = json.loads(params_str)
# {'symbol': '123456', 'type': 'limit', 'price': 123.4, 'amount': 23}
print('params_str type:{},original_params:{}'.format(type(params_str), type(original_params)))
# params_str type:<class 'str'>,original_params:<class 'dict'>

文本处理都需要加上异常处理。

07 条件与循环

if id == 0:
	print('red')
elif id == 1:
	print('yellow')
else:
	print('green')

注意:没有括号,有冒号,elif.

省略判断写法

if s:			# s是字符串
	...
if l:			# l是list
	...
if i:			# i是int
  • 循环语句
l = [1, 2, 3, 4]
for item in l:
	print(item)
  • 遍历字典
d = {'name':'jason', 'dob':'2000-01-01','gender':'male'}
# 遍历key
for key in d:
    print(key)
# 遍历val
for val in d.values():
    print(val)

for key, val in d.items():
    print('key:{}, val:{}'.format(key, val))
  • 遍历list
l = [1,2,3,4,5,6,7]
for idx in range(0, len(l)):
    if (idx < 5):
        print(l[idx])

for idx, item in enumerate(l):
    if idx < 5:
        print(item)

continue和break分别是跳过该层循环和结束循环

  • while循环
l = [1,2,3,4,5,6,7]
idx = 0
while idx < len(l):
    print(l[idx])
    idx += 1

条件与循环合并

exp1 if condition else exp2 for item in iter
# 相当于:
for item in iter:
	if condition:
    	exp1
    else:
    	exp2

绘制y = 2 * |x| + 5的函数图像,已知x的集合

y = [val * 2 + 5 if val > 0 else -val * 2 + 5 for val in x]

字符串按照逗号分割单词,去掉首位空字符,并过滤掉长度小于3的单词

text = ' Today, is, Sunday'
text_list = [s.strip() for s in text.split(',') if len(s.strip()) > 3]
print(text_list)
# ['Today', 'Sunday']

给定两个列表x和y要求返回x和y自称所有元素对组成元组,想等除外。

[(xx, yy) for xx in x for yy in y if xx != yy]
l = []
for xx in x:
	for yy in y:
    	if xx != yy:
        	l.append((xx, yy))

08 异常处理

常见的三种异常类型:ZeroDivisionErrorNameerrorTypeError.

try-except

try:
    s = input('please enter two numbers separated by comma: ')
    num1 = int(s.split(',')[0].strip())		# 以逗号相隔的两个整形数字,将其提取
    num2 = int(s.split(',')[1].strip())
    ... 
except ValueError as err:
    print('Value Error: {}'.format(err))
 
print('continue')
...

except block 只接受与它相匹配的异常类型并执行,如果程序抛出的异常并不匹配,那么程序照样会终止并退出。continue 不会被打印。

其中一种解决方案,是在 except block 中加入多种异常的类型。

try:
    s = input('please enter two numbers separated by comma: ')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
    ...
except (ValueError, IndexError) as err:
    print('Error: {}'.format(err))
    
print('continue')
...

Exception 是其他所有非系统异常的基类,能够匹配任意非系统异常。那么这段代码就可以写成下面这样:

try:
    s = input('please enter two numbers separated by comma: ')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
    ...
except ValueError as err:
    print('Value Error: {}'.format(err))
except IndexError as err:
    print('Index Error: {}'.format(err))
except Exception as err:
    print('Other error: {}'.format(err))
 
print('continue')
...
import sys
try:
    f = open('file.txt', 'r')
    .... # some data processing
except OSError as err:
    print('OS error: {}'.format(err))
except:
    print('Unexpected error:', sys.exc_info()[0])
finally:
    f.close()

09 函数

def my_func(message):
    print('Got a message: {}'.format(message))
 
# 调用函数 my_func()
my_func('Hello World')
# 输出
Got a message: Hello World
  • def 是函数声名
  • my_func是函数得名称
  • message函数参数
  • print是函数主体 必须先声明再使用

设定默认参数

def func(param = 0):
	...

多态:同样的一个函数(比如这边的相加函数 my_sum()),可以同时应用在整型、列表、字符串等等的操作中。

Python 支持函数的嵌套。所谓的函数嵌套,就是指函数里面又有函数,比如:

def f1():
    print('hello')
    def f2():
        print('world')
    f2()
f1()
 
# 输出
hello
world
  • 函数的嵌套能够保证内部函数的隐私。内部函数只能被外部函数所调用和访问,不会暴露在全局作用域,因此,如果你的函数内部有一些隐私数据(比如数据库的用户、密码等),不想暴露在外,那你就可以使用函数的的嵌套,将其封装在内部函数中,只通过外部函数来访问。
  • 合理的使用函数嵌套,能够提高程序的运行效率。
def factorial(input):
    # validation check
    if not isinstance(input, int):
        raise Exception('input must be an integer.')
    if input < 0:
        raise Exception('input must be greater or equal to 0' )
    ...
 
    def inner_factorial(input):
        if input <= 1:
            return 1
        return input * inner_factorial(input-1)
    return inner_factorial(input)
 
 
print(factorial(5))

不能在函数内随便改变全局变量的值,会报错。Python 的解释器会默认函数内部的变量为局部变量,但是又发现局部变量 MIN_VALUE 并没有声明,因此就无法执行相关操作。

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    global MIN_VALUE
    ...
    MIN_VALUE += 1
    ...
validation_check(5)

global 关键字,并不表示重新创建了一个全局变量 MIN_VALUE,而是告诉 Python 解释器,函数内部的变量 MIN_VALUE,就是之前定义的全局变量,并不是新的全局变量,也不是局部变量。这样,程序就可以在函数内部访问全局变量,并修改它的值了。

对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上 nonlocal 这个关键字:

def outer():
    x = "local"
    def inner():
        nonlocal x # nonlocal 关键字表示这里的 x 就是外部函数 outer 定义的变量 x
        x = 'nonlocal'
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: nonlocal

如果不加上 nonlocal 这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量。

def outer():
    x = "local"
    def inner():
        x = 'nonlocal' # 这里的 x 是 inner 这个函数的局部变量
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: local
  • 闭包(closure) 闭包其实和刚刚讲的嵌套函数类似,不同的是,这里外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。
def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是 exponent_of 函数
 
square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方 
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>
 
cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>
 
print(square(2))  # 计算 2 的平方
print(cube(2)) # 计算 2 的立方
# 输出
4 # 2^2
8 # 2^3

这里外部函数 nth_power() 返回值,是函数 exponent_of(),而不是一个具体的数值。需要注意的是,在执行完square = nth_power(2)和cube = nth_power(3)后,外部函数 nth_power() 的参数 exponent,仍然会被内部函数 exponent_of() 记住。这样,之后我们调用 square(2) 或者 cube(2) 时,程序就能顺利地输出结果,而不会报错说参数 exponent 没有定义了。

上面的闭包也可以使用以下形式:

def nth_power_rewrite(base, exponent):
    return base ** exponent
# 不适用闭包
res1 = nth_power_rewrite(base1, 2)
res2 = nth_power_rewrite(base2, 2)
res3 = nth_power_rewrite(base3, 2)
...
 
# 使用闭包
square = nth_power(2)
res1 = square(base1)
res2 = square(base2)
res3 = square(base3)
...

匿名函数lambda

全部评论

相关推荐

牛客279957775号:铁暗恋
点赞 评论 收藏
分享
害怕一个人的小黄鸭胖乎乎:笑死了,没有技术大牛,招一堆应届生,不到半年,代码就成屎山了
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务