Basic Python
1.1 解释性语言
Python 作为一种解释型语言,不同于编译型语言,其主要区别在于代码执行方式。解释型语言在执行程序时通过解释器逐行将代码转换为机器语言,并立刻执行;而编译型语言在执行之前需要经过编译器将整个代码文件编译为机器语言的可执行文件,然后再执行。
下面是它们的主要区别:
1)解释型语言(如Python):执行时逐行编译,通常无需编译步骤。
2)编译型语言(如 C++):在运行前整个程序都需要编译成机器码,产生可执行文件。
3)错误处理:解释型语言在运行时发现错误,而编译型语言在编译阶段就能找到大部分错误。
4)执行速度:编译型语言通常比解释型语言执行更快,因为编译后的代码是直接的机器码。
5)平台依赖性:编译型语言生成的平台特定可执行文件,解释型语言则更为跨平台,因为只需要对应平台的解释器。
1.2 is
and ==
在 Python 中, is
和 ==
这两个操作符均用于比较对象是否相等,注意”相同”和”相等”是不一样的,而且它们的用途和工作方式不同。在实际开发过程中主要使用 ==
来进行值比较,而使用 is
来判断对象的唯一性,比如检查某个对象是否为 None
。
is
是用来判断两个变量是否引用了同一个对象,它看的是对象的内存地址。
==
是用来判断两个对象是否具有相同的值。
1 | x = None |
1.3 any()
and all()
在 Python 中,any()
和 all()
是两个内置函数,用于对可迭代对象(如列表、元组、集合、字典、字符串等)中的元素执行布尔运算。
any()
函数用于判断:给定的可迭代对象是否至少有一个元素为 True(或真值)。如果可迭代对象为空,返回 False。any()
函数在遍历可迭代对象时,只要找到一个 True 值就立即返回 True,不再继续遍历剩余的元素。all()
函数用于判断给定的可迭代对象中的所有元素是否都为 True(或真值)。如果可迭代对象为空,则返回 True。all()
函数在遍历可迭代对象时,只要找到一个 False 值就立即返回 False,不再继续遍历剩余的元素。
1 | print(any([0, False, None])) # False,None视为False |
1.4 read
, readline
and readlines
在 Python 中, read
,readline
和 readlines
方法用于从文件中读取内容,它们有不同的用途和返回类型:
read
- 用法:
file.read(size=-1)
- 功能:一次性读取整个文件内容,如果指定 size,则读取指定的字节数。
- 返回:一个字符串,包含整个文件的内容或指定的字节内容。
- 适用:需要处理整个文件的场景,如读取配置文件等,对于大型文件,
read
可能导致内存不足。
readline
- 用法:
file.readline(size=-1)
- 功能:读取文件中的一行内容,读取完成后,文件指针会移动到下一行。
- 返回:一个字符串,该字符串代表文件中的一行内容。
- 适用:逐行读取文件的场景,如读取数据进行处理。适用结合循环进行文件读取,处理大文件而不会占用过多内存。
readlines
- 用法:
file.readlines(sizehint=-1)
- 功能:一次性读取文件中所有行,并把它们作为字符串列表返回。
- 返回:一个列表,列表中的每个元素是文件中的一行内容。
- 适用:需要处理文件所有行但是操作较为简单的场景,如将文件内容保存到列表中批量处理,与
read
类似,需要小心内存问题。
1 | # 使用 read() 方法 |
1.5 copy()
and deepcopy()
- 浅拷贝(shallow copy):创建一个新的对象,但不复制内部嵌套的对象。新对象只复制了原对象的引用,因此对任一对象中内部嵌套的改动会影响到另一个对象,即浅拷贝和原对象共享嵌套列表。
- 深拷贝(deep copy):创建一个新的对象,同时递归地复制所有嵌套的对象。这样即使内部嵌套对象被修改,原对象和复制对象也不会相互影响。
1 | import copy |
2 Data structures
Python 主要有以下几种内置的数据结构:
- 列表(List):有序、可变的序列,使用方括号
[]
表示。 - 元组(Tuple):有序、不可变的序列,使用小括号
()
表示。 - 集合(Set):无序、唯一元素的集合,使用大括号
{}
表示,注意空集合使用set()
表示而不是{}
,因为{}
用来表示空字典。 - 字典(Dictionary):键值对的集合,使用大括号
{}
表示。 - 字符串(String):有序、不可变的字符序列,使用单引号
''
或者双引号“”
表示。
2.1 List
- 列表是 Python 中最通用的数据结构,支持多数数据类型作为元素。
- 可通过索引和切片操作列表。
- 支持各种方法如
append()
,remove()
,pop()
,sort()
,reverse()
等。
Python 中常见的对于列表的操作方法有:
append()
:将一个元素添加到列表的末尾。这里元素可以是任何类型的对象。1
2
3my_list = [1, 2, 3]
my_list.append('a')
print(my_list) # [1, 2, 3, 'a']insert()
:在指定位置插入一个元素。需要传递两个参数,第一个是索引位置,第二个是要添加的元素。1
2
3my_list = [1, 2, 3]
my_list.insert(1, 'a') # 在索引 1 位置插入 'a'
print(my_list) # [1, 'a', 2, 3]extend()
:将另外一个列表中的所有元素添加到当前列表末尾,类似于列表的拼接操作。1
2
3my_list = [1, 2, 3]
my_list.extend([4, 5, 6])
print(my_list) # [1, 2, 3, 4, 5, 6]remove()
:从列表中删除第一个匹配到的项,如果该项不存在,则抛出ValueError
,输入的参数为数值。1
2
3my_list = [1, 2, 3]
my_list.reomve(2)
print(my_list) # [1, 3]del
:用于删除列表中的一个或者多个元素,也可以用于删除变量。1
2
3
4my_list = [1, 2, 3, 4]
del my_list[0]
del my_list[1:3]
print(my_list) # [4]pop()
:默认删除列表中的最后一项。如果指定索引,则删除指定索引项,如果索引不存在则抛出ValueError
。1
2
3
4my_list = [1, 2, 3, 4]
my_list.pop()
my_list.pop(2)
print(my_list) # [1, 2]sort()
:对列表中数字进行排序,输入参数为布尔值。1
2
3my_list = [1, 6, 9, 4, 0]
my_list.sort(reverse=True)
print(my_list) # [9, 6, 4, 1, 0]reverse()
:对列表进行反向排列。1
2
3my_list = [1, 6, 9, 4, 0]
my_list.sort(reverse=True)
print(my_list) # [0, 4, 9, 6, 1]
2.2 Tuple
- 元组一旦创建,就不能修改,这使得它们是不可变的。
- 因为不可变,元组更安全,尤其在作为字典的键时。
- 操作方式与列表相似,如通过索引|和切片读取。
Python 中元组解封装(tuple unpacking)是一种常用的操作方式,即将一个元组的多个元素分别赋值给对应数量的变量进行操作。
函数返回多个值
1
2
3
4def get_results():
return (4646.465, 'a', 46)
a, b, c = get_results()
print(f"{a=}, {b=}, {c=}")循环中解封装
1
2
3grades = [('math', 99), ('physics', 98), ('chemistry', 97)]
for subject, grade in grades:
print(f"{subject}:{grade=}\n")集成解封装
1
2a, *b, c = (1, 2, 3, 4, 5, 6)
print(f"a: {a}, b: {b}, c: {c}")
2.3 Set
- 集合用于去重,对大型数据集进行去重操作尤其高效。
- 支持各种集合操作如并集、交集、差集和对称差等。
- 元素需是不可变类型,如数值、字符串或元组。
集合一般可以用于对列表或者元组进行去重或进行集合操作。
1 | list_1 = [1, 2, 3, 4, 5, 5, 5] |
2.4 Dictionary
- 字典是键值对的集合,键是唯一的且不可变,值可以是任何类型。
- 可以快速查找、添加、删除元素。
- 常用于需要频繁查找操作的场景。
Python 的字典是一种可变、无序、键值对的数据结构。它允许我们通过键快速查找对应的值,键必须是唯一且不可变的(比如字符串、数字、元组等),而值可以是任意的 Python 对象(例如字符串、数字、列表,甚至是另一个字典) 。基本用法包括:
创建字典:使用大括号
{}
或dict()
函数。1
2dict_1 = {'name': 'Ross', 'age': 26}
dict_2 = dict(name='Jack', age=46)访问和修改元素:通过键来访问对应的值,也可以新增或修改元素。
keys()
等函数返回的是视图对象(view object),反映的是字典的动态变化。1
2
3
4
5
6dict_1 = {'name': 'Ross', 'age': 26}
print(dict_1.keys()) # dict_keys(['name', 'age'])
dict_1.update({'age': 27})
print(dict_1.values()) # dict_values(['Ross', 27])
dict_1['city'] = 'New York'
print(dict_1.items()) # dict_items([('name', 'Ross'), ('age', 27), ('city', 'New York')])删除元素:使用
del
关键字或pop()
方法。1
2
3
4dict_1 = {'name': 'Ross', 'age': 26, 'city': 'New York'}
dict_1.pop('name')
del dict_1['age']
print(dict_1) # {'city': 'New York'}遍历字典:使用
for
循环可以遍历字典的键、值或键值对。1
2
3dict_1 = {'name': 'Ross', 'age': 26, 'city': 'New York'}
for key, value in dict_1.items():
print(key, value) # name Ross age 26 city New York
2.5 String
- 字符串是不可变的,这意味着每次修改字符串都会创建一个新字符串。
- 支持格式化、拼接、多种方法例如
find()
,replace()
,split()
,join()
等。 - 作为一种序列,也可以通过索引和切片来读取字符。
Python 中使用单引号 ''
或者双引号 “”
表示字符串,选择使用单引号还是双引号主要是为了程序中字符串还包含另一种引号如“It's a string!”
或 ‘It\'s a string!’
。基本用法包括:
多行注释。
1
2
3multi_line = """ This is a
multi-line
string"""字符串拼接。
1
2
3
4
5
6
7
8
9
10
11# +
first = "Hello"
second = "World!"
res = first + ' ' + second # Hello World!
# formatted
name = "Ross"
age = 26
formatted_res = f"{name} is {age} years old." # Ross is 26 years old.
# join()
words = ["Hello", "you", "guys"]
sentence = " ".join(words) # Hello you guys字符串操作
upper()
:将字符串中的所有字母转换为大写。1
2
3string = "Hello, World!"
uppercase_string = string.upper()
print(uppercase_string) # "HELLO, WORLD!"lower()
或者casefold()
:将字符串中的所有字母转换为小写,casefold()
转化力度更强,可以处理国际化语言。1
2
3string = "Hello, World!"
lowercase_string = string.lower()
print(lowercase_string) # "hello, world!:capitalize()
:将字符串的首字母大写,其他字母小写。1
2
3string = "hello, world!"
capitalized_string = string.capitalize()
print(capitalized_string) # "Hello, world!"title()
:将字符串中每个单词的首字母大写。1
2
3string = "hello, world!"
title_string = string.title()
print(title_string) # "Hello, World!"join()
:将列表中的元素以指定的分隔符进行连接。参数是可迭代的对象,如列表,元组等,元素必须是字符串。常用于生成某字符分割的字符串,如文件路径或者 URL 参数等。1
2
3
4
5
6
7
8
9
10lst = ['apple', 'huawei', 'xiaomi']
phone = ', '.join(lst) # "apple, huawei, xiaomi"
path = ['home', 'user', 'document']
abs_path = '/'.join(path) # "home/user/document"
params = {'param1': 'value1', 'param2': 'value2'}
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
# "param1=value1¶m2=value2"split(sep=None, maxsplit=-1)
:sep
是分隔符,默认为None
,表示以空白分隔符,如空格、制表符、换行符等作为分割符,maxsplit
为最大分割次数,默认为不限制分割次数。1
2
3
4
5
6
7seq = 'apple huawei xiaomi'
s = seq.split() # ['apple', 'huawei', 'xiaomi']
s = seq.split(' ', 1) # ['apple', 'huawei xiaomi']
import re
seq = 'apple,huawei;xiaomi'
s = re.split(r'[;,]', seq) # ['apple', 'huawei', 'xiaomi']strip()
:删除字符串两边的空格字符(空格、制表符等)。使用lstrip()
删除左边的空格字符,rstrip()
则是删除右边的空格字符。1
2
3my_string = " Hello, World! "
trimmed_string = my_string.strip()
print(trimmed_string) # "Hello, World!"replace(old, new, count)
:替换字符串中的某个子字符串为一个新的子字符串,count
为替换的次数,默认为替无限制替换。需要注意的是在 Python 中字符串为不可变对象,所以replace()
是返回新生成的一个字符串而并没有对原字符串进行改变。1
2
3
4
5
6
7
8
9
10text = "hello hello hello"
# 只替换第一个 "hello"
new_text = text.replace("hello", "hi", 1)
print(new_text) # "hi hello hello"
import re
text = "fuck fucking fucked"
# 将所有 'u' 替换为 '*'
new_text = re.sub(r'u', '*', text)
print(new_text) # "f*ck f*cking f*cked"
3. Object-oriented programming
Python 的 OOP (Object-oriented programming) 是一种编程范式,它通过使用”类”和”对象”来帮助程序员组织代码,提高代码的可重用性、可维护性和扩展性。在 Python 中,OOP 包括四大主要特性:
- 封装(Encapsulation):通过把数据和操作数据的方法封装在一个类中,保护数据不被外界意外修改。
- 继承(Inheritance):通过定义新的类来继承已有类的属性和方法,从而实现代码复用。
- 多态(Polymorphism):通过不同的类实现相同的方法,使得相同的操作作用于不同的对象产生不同的结果。
- 抽象(Abstraction):通过抽象类和接口来定义通用方法,隐藏复杂的实现细节。
类 (Class) 和对象 (Object) 是面向对象编程 (OOP) 的两个核心概念。在Python中:
- 类是一种模板,它定义了对象的属性和行为。
- 对象是类的实例,通过类创建具体的对象,并拥有类中定义的属性和方法。
通俗地说,类就像一个蓝图,而对象则是根据这个蓝图制造出来的具体”产品”。定义类时,使用关键字 class
,类包含方法(函数)和属性(变量)。通过调用类进行实例化对象的创建。
- 类变量是在类级别声明的,所有实例共享,比如统计所有创建的对象数量。
- 实例变量是在实例级别声明的,每个对象拥有独立的值。
1 | class Student: |
__init__
方法在 Python 中是类的构造方法,或者说是初始化方法。它在你创建一个类的实例时被自动调用,用来初始化实例的属性。简单来说,当你使用类创建一个对象时, __init__
方法会帮助你赋值对象的初始属性。避免手动逐个对属性进行赋值,提高代码的可读性和维护性。self
是一个重要的概念,它代表类的实例。用简单的话来说,它是引用类当前实例的方式。当我们定义一个类的方法时,必须把 self
作为第一个参数,因为这让方法能够访问该实例的属性和其他方法,self
只是一个约定俗成的命名方式,使用任何名称都可以代替。除了实例方法(instance method)外,Python 还有类方法(class method)和静态方法(static method)。类方法使用 cls
作为第一个参数,代表类本身,而静态方法则不需要 self
或 cls
参数。例如:
1 | class MyClass: |
3.1 Encapsulation
Python 面向对象的封装特性是指将对象的状态(属性)和行为 (方法) 包装在一个类中,并限制访问,以确保数据的隐藏和安全。封装通过在类中定义私有属性和方法,并提供公共的方法(getter 和 setter)来访问和修改这些属性,从而实现对数据的保护和控制。
- 封装的好处
- 数据隐藏和保护:封装使得内部状态或行为对外部不可见,防止外部代码直接访问或修改内部数据,从而提高了安全性。
- 简化接口:通过提供简洁明了的公共接口,可以隐藏对象的复杂实现细节,使得使用对象更加容易理解和使用。
- 代码模块化:封装有助于将代码组织成独立的模块,提高代码的可读性、可维护性和可重用性。
- 如何实现封装
在 Python 中,封装主要通过定义私有和保护的属性和方法来实现:
- 私有属性和方法:通过在属性或方法名前加双下划线
__
,例如__private_attribute
,让它们成为私有的,这意味着只能在类的内部访问。 - 保护的属性和方法:通过在属性或方法名前加单下划线
_
,例如_protected_attribute
,虽然这是一个约定,但它表示变量不应该从类的外部直接访问。
1 | class MyClass: |
3.2 Inheritance
继承是复用代码的一个重要特性。我们可以创建一个基类,并在此基础上扩展新的子类。子类会继承基类的属性和方法,并可以重写或扩展这些方法。Python 支持多重继承,即一个子类可以同时从多个父类继承属性和方法。
1 | class Parent1: |
当一个类继承了多个父类时,Python 使用一种称为 C3 线性化(也叫MRO: Method Resolution Order)的方法来确定方法和属性的继承顺序。你可以使用 ClassName.__mro__
或 ClassName.mro()
方法来查看某个类的 MRO:
1 | print(Child.__mro__) |
Python 提供了 super()
函数,可以按照 MRO 顺序调用父类的方法。对于多重继承尤其有用,因为它可以帮助我们避免显式调用特定父类的方法,从而避免混乱:
1 | class A: |
3.3 Polymorphism
Python 的面向对象编程(OOP)中的多态特性,指的是不同对象对同一方法的不同实现。在应用中,多态性可以让我们在不改变代码主体结构的情况下,使用不同的对象来实现相同的接口或方法。这使得代码更具灵活性和可扩展性。多态通常是通过继承实现的。子类继承父类并且重写 (override) 父类的方法,这样在运行时,调用的方法根据对象的实际类型来确定。例如:
1 | from abc import ABC, abstractmethod |
在这里多态性允许父类的引用指向子类的对象,这里 Dog()
和 Cat()
都继承 Animal()
,所以它们的实例可以看作是 Animal
类。函数签名中虽然注释了需要传入 Animal
类的对象,但是只要是其子类或者兼容的对象都是可以正常工作的。具体而言,animal
是 Animal
类型的引用,但它实际上指向了 Dog
类的一个实例对象 dog
。尽管 animal
被声明为 Animal
类型,但由于 Dog
类实现了 sound()
方法,当我们调用 animal.sound()
时,执行的是 Dog
类的 sound()
方法,输出 "Woof"
。我们也可以使用抽象基类 (ABC模块) 来实现多态。抽象基类能够定义抽象方法,子类必须实现这些抽象方法,否则抛出 TypeError
。
鸭子类型(duck typing)是一种动态类型检查的方法,广泛应用于动态语言如 Python 中。它的核心理念是:”如果它像鸭子一样走路,并且像鸭子一样叫,那么它很可能就是一只鸭子”。从编程角度来看,鸭子类型关注对象的行为 (方法和属性)而不是其实际类型。只要对象能够响应我们所期望的方法,就可以使用它,而不需要明确声明或检查类型。这种方式让代码更加灵活和简洁。
1 | class Duck: |
优点:
- 灵活性:代码不依赖对象的具体类型,而依赖对象的行为。只要对象提供了所需的方法或属性,就可以使用它。
- 减少耦合:不需要对象之间有复杂的继承关系,降低了代码之间的耦合度。
- 代码简洁:不必编写额外的类型检查代码或继承结构,可以专注于实现对象的行为。
缺点:
- 潜在的运行时错误:由于类型不在编译时检查,可能会导致在运行时才发现对象不具备所需的方法或属性,造成错误。
- 可读性降低:因为类型不明确,有时在大型项目中可能会让代码更难以理解,尤其是对于代码维护者来说,必须知道哪些对象应该实现哪些行为。
- 不适用于所有场景:对于非常复杂的系统,鸭子类型可能不够严格,可能需要明确的接口和类型定义来保证代码的正确性。
3.4 Abstraction
抽象是指将复杂的系统隐藏在一个简单的接口后面,只呈现出必要的功能细节,而将具体的实现细节隐藏起来。通过抽象,可以使代码更加模块化、可维护性更好,也更符合人类的思维模式。
- 提高代码可维护性:代码的逻辑与细节实现分离,便于管理和维护。
- 增强代码复用性:将共有的操作抽象出来,在多个地方复用同一个抽象接口。
- 易于扩展:在不影响已有代码的情况下,可以轻松地扩展新功能,只需实现新的具体类。