python进阶(已完结)🧑💻
第一章函数式编程
1. 函数式编程的定义
函数式编程是一种编程风格(顾名思义)是基于函数的。
函数式编程的关键部分是高阶函数。在上一课中,我们已经将这个想法简单地看作是对象的功能。高阶函数将其他函数作为参数,或将其作为结果返回。
def apply_twice(func, arg):
return func(func(arg))
def add_five(x):
return x + 5
print(apply_twice(add_five, 10))
# result: 20
函数 apply_twice 将另一个函数作为参数,并在其内部调用两次。
【填空题】以下代码的输出是什么?
def test(func, arg):
return func(func(arg))
def mult(x):
return x * x
print(test(mult, 2))
# result: 16
纯函数
函数式编程致力于使用纯函数。纯函数不存在副作用,其返回值仅取决于自身的参数。
这与数学工作中的函数类似,例如,对于相同的 x,cos(x) 始终返回相同的结果。
纯函数的例子:
def pure_function(x, y):
temp = x + 2*y
return temp / (2*x + y)
非纯函数的例子:
some_list = []
def impure(arg):
some_list.append(arg)
上面的函数不是纯函数,因为它改变了 some_list 的状态。
纯函数的优点与缺点
纯函数的特点: 纯函数的一些主要缺点
2. 函数表达式Lambdas
Lambda 表达式 正常情况下创建一个函数(使用 def)会自动将其分配给一个变量。
这与其他对象(如字符串和整数)的创建有所不同,它们可以在运行过程中创建,且不必分配给变量。
使用 lambda 语法来创建函数,这种方式创建的函数被称作匿名函数。
在将一个简单函数作为参数传递给另一个函数时,这种方法最为常用。
语法如下: lambda 参数列表: 表达式
例子:
# 定义一个函数my_func,它接受两个参数,一个是函数f,另一个是参数arg
def my_func(f, arg):
return f(arg)
# 调用my_func函数,传入一个匿名函数(lambda表达式)和参数5
my_func(lambda x, y: x+y, 5)
注意: 使用lambda表达式并不能提高代码的运行效率,它只能让你的代码看起来简洁一些。
Lambda 函数不如命名函数强大,因为它们通常只能处理需要单一表达式的事情,相当于一行代码。
#命名函数
def polynomial(x):
return x**2 + 5*x + 4
print(polynomial(-4))
#lambda
print((lambda x: x**2 + 5*x + 4) (-4))
# result: 0, 0
在上面的代码中,我们创建了一个匿名函数,并用参数调用它。
可以将 Lambda 函数分配给变量,并像普通函数一样使用。
double = lambda x: x * 2
print(double(7))
# result: 14
然而,很少会这样去做——通常情况下,使用 def 定义一个函数会更好。
3. 内置函数map和filter
map
内置函数 map
和 filter
是在列表(或类似的被称为迭代的对象)上运行的极为有用的高阶函数。
函数 map
接收一个函数和一个迭代器作为参数,进而返回一个新的迭代器,将该函数应用于每个参数。
map()
将函数func
应用于序列seq
中的所有元素。
def add_five(x):
return x + 5
nums = [11, 22, 33, 44, 55]
result = list(map(add_five, nums))
print(result)
# result: [16, 27, 38, 49, 60]
通过使用 lambda 语法,我们可以更容易地获得相同的结果。
nums = [11, 22, 33, 44, 55]
result = list(map(lambda x: x+5, nums))
print(result)
# result: [16, 27, 38, 49, 60]
为了将结果转换成列表,我们使用了 list 函数。
filter
filter()
是一个内置函数。它的主要作用是从一个可迭代对象(如列表、元组、字符串等)中过滤出满足特定条件的元素,返回一个迭代器。 语法:
filter(function, iterable)
- function -- 判断函数。
- iterable -- 可迭代对象
nums = [11, 22, 33, 44, 55]
res = list(filter(lambda x: x%2==0, nums))
print(res)
# result: [22, 44]
和 map 相同,若要打印结果,需将结果显式地转换为列表。
4. 生成器
for循环的本质
x = [1, 2, 3]
for elem in x: # 定时垃圾回收机制:没有引用指向这个对象,则被回收
print(elem)
实际上 for 循环本质上做了三件事:
- 调用 iter()方法可将可迭代对象转换为迭代器,例如:iter(x)。
- 持续调用 next()方法,以获取迭代器中的下一个元素值。
- 处理 StopIteration 异常
因此上述例子完全等价于:
# 首先获得迭代器对象:
x = [1, 2, 3]
it = iter(x)
# 循环:
while True:
try:
# 获得下一个值:
elem = next(it)
print(elem)
except StopIteration:
# 遇到 StopIteration 就退出循环
break
生成器
生成器是一种可迭代的类型,如列表或元组。
与列表不同的是,它们不允许使用任意索引,但是它们仍然可以通过 for 循环迭代。
可以使用 函数
和 yield
语句来创建它们。
def countdown():
i=5
while i > 0:
yield i
i -= 1
for i in countdown():
print(i)
result: 5, 4, 3, 2, 1
由于它们一次产生一个项目,所以生成器不具有列表的内存限制。
事实上,他们可以是无限的!
def infinite_sevens():
while True:
yield 7
for i in infinite_sevens():
print(i)
# result: 7, 7, 7, 7 ...
简而言之,生成器允许你声明一个像迭代器一样的函数,也就是说它可以在 for 循环中使用。
有限的生成器可以通过将它们作为参数传递给 list
函数来转换成列表。
def numbers(x):
for i in range(x):
if i % 2 == 0:
yield i
print(list(numbers(11)))
# result: [0, 2, 4, 6, 8, 10]
使用生成器可以提高性能,这是懒惰(按需)生成值的结果,这意味着更低的内存使用率。而且,在开始使用之前,我们不需要等到所有的元素都被生成。
5. 装饰器
装饰者 是修改其他函数的功能的函数。装饰器有助于让我们的代码更简短。
当您需要扩展您不想修改的函数功能时,这是很理想的。
def decor(func):
def wrap():
print("============")
func()
print("============")
return wrap
def print_text():
print("Hello world!")
decorated = decor(print_text)
decorated()
"""
============
Hello world!
============
"""
我们定义了一个名为 decor 的函数,它有一个单一的参数 func。
在 decor 中,我们定义了一个名为 wrap 的嵌套函数。
wrap函数将打印一个字符串,然后调用 func(),并打印另一个字符串。
我们可以说,装饰变量是 print_text 的装饰版本 - 它是 print_text 加上一些东西。
这是通过重新赋值包含我们的函数的变量来完成的:
print_text = decor(print_text)
print_text()
"""
result:
============
Hello world!
============
"""
Python通过预先用装饰器名称和 @symbol 预定义函数定义来提供支持,以便在装饰器中包装函数。 如果我们正在定义一个函数,我们可以使用@符号来“装饰”它:
def decor(func):
def wrap():
print("============")
func()
print("============")
return wrap
@decor
def print_text():
print("Hello world!")
print_text()
"""
result:
============
Hello world!
============
"""
提示: 一个函数可以有多个装饰器。
6. 递归(Recursion)
递归是函数式编程中一个非常重要的概念。
递归的基本部分是自引用 - 调用自己的函数。它被用来解决可以被分解成相同类型的更容易的子问题的问题。
例如:
m = int(input('输入一个数字:'))
def test(x):
x += 2
if x <100:
test(x)
else:
print('最后的x为:',x)
test(m)
"""
result:
输入一个数字:20
最后的x为: 100
"""
在第一行代码中输入了一个整数。最后一行将 m 作为实际参数传递到 test () 方法。 在 test () 方法中,会先对 x 进行加 2 处理,然后进行判断。如果 x 的值小于 100,那么就再次调用这个函数,调用时以当前 x 的值作为实际参数,直到满足 x >= 100 时结束递归。即如果不满足条件就回到了最外层来调用了这个函数。
阶乘函数是递归实现的函数的典型例子,N的阶乘写作N!表示小于等于N的所有正整数的乘积。
例如,5! (5的阶乘)是 5 * 4 * 3 * 2 * 1(120)。可以这样拆解然后用递归实现, 5! = 5 * 4!,4! = 4 * 3!,3! = 3 * 2! 等等。一般来说,n! = n * (n-1)!,然而,1! = 1 被称为基准情形(base case),因为它可以被直接计算,而不用执行更多的阶乘因子。
以下是阶乘函数的递归实现。
def factorial(x):
if x == 1:
return 1
else:
return x * factorial(x-1)
print(factorial(5))
# result: 120
基准情形充当递归的退出条件。
递归函数可以是无限的,就像无限循环一样。当你忘记设置基准情形(base case)时,经常发生这种情况。
以下是阶乘函数的错误版本。它没有基准情形(base case),所以它运行,直到解释器内存不足而崩溃。
def factorial(x):
return x * factorial(x-1)
print(factorial(5))
# result: RuntimeError: maximum recursion depth exceeded
在上述代码中,由于缺少基准情形(也就是没有明确当 x 达到某个特定值时不再进行递归调用,直接返回结果的条件),这个函数会不停地递归调用自身,一直消耗内存资源,直到解释器的内存被耗尽,进而导致程序崩溃。
正确的阶乘函数应该像下面这样,设置好基准情形:
def factorial(n):
if n == 0: # 这就是基准情形,当n为0时,直接返回1,终止递归
return 1
return n * factorial(n - 1)
总之,编写递归函数时一定要记得设置合理的基准情形,避免出现因无终止条件而致使程序出现内存不足崩溃等不良后果。
递归也可以是间接的。一个函数可以调用第二个函数,第二个函数再调用第一个函数,依此类推。这可以发生在任何数量的函数上。
例如:
def is_even(x):
if x == 0:
return True
else:
return is_odd(x-1)
def is_odd(x):
return not is_even(x)
print(is_odd(17))
print(is_even(23))
"""
True
False
"""
7. 集合
集合是数据结构,类似于列表或字典。集合使用花括号或 set 函数创建。
它们与列表共享一些功能,例如使用 in 来检查它们是否包含特定项目。
num_set = {1, 2, 3, 4, 5}
word_set = set(["spam", "eggs", "sausage"])
print(3 in num_set)
print("spam" not in word_set)
"""
True
False
"""
要创建一个空集,必须使用 set(),如 {} 是创建一个空字典。
集合在几个方面不同于列表,但共享几个列表操作,如 len。
集合是无序的,这意味着他们不能被索引。
集合不能包含重复的元素。
由于存储的方式,检查一个项目是否是一个集合的一部分比检查是不是列表的一部分更快。
集合使用 add 添加元素,而不是使用 append 。
remove 方法从集合中删除特定的元素; pop 删除随机的元素。
nums = {1, 2, 1, 3, 1, 4, 5, 6}
print(nums)
nums.add(-7)
nums.remove(3)
print(nums)
"""
{1, 2, 3, 4, 5, 6}
{1, 2, 4, 5, 6, -7}
"""
通常使用集合来消除重复的条目。
集合可以使用数学运算进行组合。
**联合运算符 |**结合两个集合形成一个包含两个集合任一项目的新集合。
**相交运算符&**获得两个集合共有的项目
差运算符 - 获取第一集合中的项目,但不是第二集合中的项目。
对称差分运算符^ 获取任集合中非共有的项目。
first = {1, 2, 3, 4, 5, 6}
second = {4, 5, 6, 7, 8, 9}
print(first | second)
print(first & second)
print(first - second)
print(second - first)
print(first ^ second)
"""
{1, 2, 3, 4, 5, 6, 7, 8, 9}
{4, 5, 6}
{1, 2, 3}
{8, 9, 7}
{1, 2, 3, 7, 8, 9}
"""
数据结构 正如我们在前面的课程中所看到的,Python支持以下数据结构:列表,字典,元组,集合。
何时使用字典:
- 当您需要键:值对之间的逻辑关联时。
- 当您需要基于自定义密钥快速查找数据时。
- 当你的数据不断修改时。请记住,字典是可变的。
何时使用其他类型:
- 如果您有一些不需要随机访问的数据集合,请使用列表。当你需要一个简单的,可迭代的频繁修改的集合可以使用列表。
- 如果你需要元素的唯一性,使用集合。
- 当数据无法更改时使用元组。
很多时候,元组与字典结合使用,例如元组可能代表一个关键字,因为它是不可变的。
8. 内建模块 itertools
itertools
itertools 模块是一个标准库,包含了几个在函数式编程中很有用的函数。
一种类型的函数是无限迭代器。
coun
函数从一个值无限增加。
cycle
函数无限次迭代(例如列表或字符串)。
repeat
函数重复一个对象,无论是无限还是特定的次数。
from itertools import count
for i in count(3):
print(i)
if i >=11:
break
# result: 3, 4, 5, 6, 7, 8, 9, 10, 11
itertools 中有许多功能可以在迭代器上运行,类似于映射和过滤。
例如:
takewhile
- 当判定函数(返回值为 True 或 False)保持为True时,从迭代中取得项目;
chain
- 将几个迭代结合成一个长整数;
accumulate
- 以可迭代的方式返回一个正在运行的值。
from itertools import accumulate, takewhile
nums = list(accumulate(range(8)))
print(nums)
print(list(takewhile(lambda x: x<= 6, nums)))
"""
[0, 1, 3, 6, 10, 15, 21, 28]
[0, 1, 3, 6]
"""
itertool中还有几个组合函数,比如 product 和 permutation。
当你想用一些项目的所有可能的组合来完成任务时使用。
from itertools import product, permutations
letters = ("A", "B")
print(list(product(letters, range(2))))
print(list(permutations(letters)))
"""
[('A', 0), ('A', 1), ('B', 0), ('B', 1)]
[('A', 'B'), ('B', 'A')]
"""
第二章面向对象编程
1. 类的定义
类
我们先前看过两种编程模式 - 命令式(使用语句,循环和函数)和 函数式(使用纯函数,高阶函数和递归)。
另一个非常流行的范例是面向对象编程(OOP)。
对象是使用类来创建的,而这些类实际上是 OOP 的模具。
这个类描述了这个对象是什么,但是和对象本身是分开的。换句话说,一个类可以被描述为一个对象的蓝图,描述或定义。
你可以使用相同的类作为创建多个不同对象的蓝图。
类是使用关键字 class 和一个包含类方法的缩进块创建的。
下面是一个简单的类和它的对象的例子。
class Cat:
def __init__(self, color, legs):
self.color = color
self.legs = legs
felix = Cat("ginger", 4)
rover = Cat("dog-colored", 4)
stumpy = Cat("brown", 3)
print("felix:",felix.__dict__) #dict是用来存储对象属性的一个字典,其键为属性名,值为属性的值.
print("rover:",rover.__dict__)
print("stumpy:",stumpy.__dict__)
这段代码定义了一个名为 Cat 的类,它有两个属性:color 和 legs。 然后这个类被用来创建这个类的3个独立的对象.
__init__
__init__
方法是一个类中最重要的方法。
这是在创建类的实例(对象)时使用类名称作为函数调用的。
所有的方法都必须以 self 作为自己的第一个参数,虽然它没有被明确地传递,但是 Python 为自己添加了自变量;
在调用方法时,不需要包含它。在一个方法定义中,self 指的是调用该方法的实例。
注意:
__init__
是两个下划线, 不是一个下划线 init 类的实例具有属性,这些属性是与它们相关联的数据片段。
在这个例子中,Cat 实例具有属性 color 和 legs。这些可以通过在一个实例之后加一个点和属性名来访问。
在 __init__
方法中,可以使用 self.attribute 来设置实例属性的初始值。
class Cat:
def __init__(self, color, legs):
self.color = color
self.legs = legs
felix = Cat("ginger", 4)
print(felix.color)
# result: ginger
在上面的例子中,
__init__
方法接受两个参数并将它们分配给对象的属性。__init__
方法被称为类构造函数。
方法
类可以定义方法来为其添加功能。
请记住,所有的方法必须有 self 作为他们的第一个参数。
这些方法使用与属性相同的点语法进行访问。
class Dog:
def __init__(self, name, color):
self.name = name
self.color = color
def bark(self):
print("Woof!")
fido = Dog("Fido", "brown")
print(fido.name)
fido.bark()
"""
Fido
Woof!
"""
类还可以具有通过在类的主体内分配变量而创建的类属性。这些可以从类的实例或类本身访问。
class Dog:
legs = 4
def __init__(self, name, color):
self.name = name
self.color = color
fido = Dog("Fido", "brown")
print(fido.legs)
print(Dog.legs)
"""
4
4
"""
类属性由类的所有实例共享。
尝试访问未定义实例的属性会导致 AttributeError。这也适用于你调用未定义的方法。
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
rect = Rectangle(7, 8)
print(rect.color)
# result: AttributeError: 'Rectangle' object has no attribute 'color'
2. 三大特性之继承
继承提供了一种在类之间共享功能的方法。
想象几个类,Cat,Dog,Rabbit等。虽然它们在某些方面可能有所不同(只有 Dog 可能有 bark 方法),但它们可能在其他方面相似(都具有 color 和 name 的属性)。
这种相似性可以通过使它们全部从包含共享功能的超类 Animal 中继承来表示。
要从另一个类继承一个类,请将超类名放在类名后面的括号中。
class Animal:
def __init__(self, name, color):
self.name = name
self.color = color
class Cat(Animal):
def purr(self):
print("Purr...")
class Dog(Animal):
def bark(self):
print("Woof!")
fido = Dog("Fido", "brown")
print(fido.color)
fido.bark()
"""
brown
Woof!
"""
超类与子类
从另一个类继承的类称为子类。
被继承的类被称为超类。
如果一个类继承了另一个具有相同属性或方法的类,它的属性和方法将覆盖它们。
class Wolf:
def __init__(self, name, color):
self.name = name
self.color = color
def bark(self):
print("Grr...")
class Dog(Wolf):
def bark(self):
print("Woof")
husky = Dog("Max", "grey")
husky.bark()
# result: Woof
在上面的例子中,Wolf 是超类,Dog 是子类。
间接继承
继承也可以是间接的。一个类B继承类A,而类C也可以继承类B。
class A:
def method(self):
print("A method")
class B(A):
def another_method(self):
print("B method")
class C(B):
def third_method(self):
print("C method")
c = C()
c.method()
c.another_method()
c.third_method()
"""
A method
B method
C method
"""
但是,不允许循环继承。
super()
super 函数是一个与父类继承相关的函数。它可以用来在对象的超类中找到具有特定名称的方法。
class A:
def spam(self):
print(1)
class B(A):
def spam(self):
print(2)
super().spam()
B().spam()
"""
2
1
"""
super().spam() 是调用超类的 spam 方法。
3. 魔术方法和操作符重载
魔术方法
魔术方法是在名称的开始和结尾都有双下划线的特殊方法。
到目前为止,我们唯一遇到的是 __init__
,但还有其他几个。
它们被用来创建不能用普通方法表示的功能。
它们的一个常见用途是运算符重载。
这意味着为自定义类定义运算符,允许使用 + 和 * 等运算符。
例子中魔术方法是 __add__
重载 +。
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
first = Vector2D(5, 7)
second = Vector2D(3, 9)
result = first + second
print(result.x)
print(result.y)
"""
8
16
"""
__add__
方法允许为我们的类中的 + 运算符定义自定义行为。 正如你所看到的,它添加了对象的相应属性并返回一个包含结果的新对象。 一旦定义了,我们就可以将这个类的两个对象相加。
常见的魔术方法
常见的魔术方法:
__sub__
对应 -__mul__
对应 *__truediv__
对应 /__floordiv__
对应 //__mod__
对应 %__pow__
对应 **__and__
对应 &__xor__
对应 ^__or__
对应 |
class SpecialString:
def __init__(self, cont):
self.cont = cont
def __truediv__(self, other):
line = "=" * len(other.cont)
return "\n".join([self.cont, line, other.cont])
spam = SpecialString("spam")
hello = SpecialString("Hello world!")
print(spam / hello)
"""
spam
============
Hello world!
"""
在上面的例子中,我们为我们的类 SpecialString 定义了除法操作。
重载比较运算
Python 也为比较运算提供了魔术方法。
__lt__
对应 <__le__
对应 <=__eq__
对应 ==__ne__
对应 !=__gt__
对应 >__ge__
对应 >=
如果 __ne__
没有被实现,它将返回 __eq__
相反的结果。
其他比较运算符之间没有其他关系。
class SpecialString:
def __init__(self, cont):
self.cont = cont
def __gt__(self, other):
for index in range(len(other.cont)+1):
result = other.cont[:index] + ">" + self.cont
result += ">" + other.cont[index:]
print(result)
spam = SpecialString("spam")
eggs = SpecialString("eggs")
spam > eggs
"""
>spam>eggs
e>spam>ggs
eg>spam>gs
egg>spam>s
eggs>spam>
"""
如您所见,您可以为重载操作符定义任何自定义行为。
容器式重载
有几个神奇的方法使类像容器一样行事。
__len__
对应 len()__getitem__
对应 获取索引__setitem__
对应 分配索引值__delitem__
对应 删除索引值__iter__
对应 迭代对象(例如for循环)__contains__
对应 in
还有很多其他的魔术方法,我们不会在这里介绍,比如将 __call__
作为函数调用对象,__int__
,__str__
等等,将对象转换为内建类型。
import random
class VagueList:
def __init__(self, cont):
self.cont = cont
def __getitem__(self, index):
return self.cont[index + random.randint(-1, 1)]
def __len__(self):
return random.randint(0, len(self.cont)*2)
vague_list = VagueList(["A", "B", "C", "D", "E"])
print(len(vague_list))
print(len(vague_list))
print(vague_list[2])
print(vague_list[2])
"""
6
7
D
C
"""
4. 对象生命周期
对象的生命周期由对象的创建,操作和销毁几个部分组成。
对象的生命周期
第一阶段是它所属的类的定义。
下一个阶段是调用 __init__
时实例的实例化。内存被分配来存储实例。
在调用 __init__
方法之前,Python首先调用 __new__
方法。
这之后,对象就可以使用了。
其他代码则可以通过调用对象和访问其属性来与对象交互。 最终,对象会完成使用,并可以被销毁。
当一个对象被销毁时,分配给它的内存被释放,并可用于其他目的。
当引用计数达到零时,就会发生对象的破坏。引用计数是引用一个对象的变量和其他元素的数量。
如果什么都没有引用它(它的引用计数为零),什么都不能与它交互,所以它可以安全地删除。
在某些情况下,两个(或更多)对象只能被彼此引用,因此也可以被删除。
del 语句会将对象的引用计数减少一个,这通常会导致删除。
del 语句的魔术方法是__del__
。
不再需要的对象删除的过程称为垃圾收集。
总之,当一个对象的引用计数被分配一个新的名字或者放在一个容器(列表,元组或者字典)中时,它的引用计数会增加。
当用 del 删除对象的引用计数时,它的引用计数会减少,引用被重新分配或引用超出作用域。当一个对象的引用计数达到零时,Python 会自动删除它。
a = 42 # 创建对象 <42>
b = a # 计数器计数增加
c = [a] # 计数器计数增加
del a # 计数器计数减少
b = 100 # 计数器计数减少
c[0] = -1 # 计数器计数减少
像 C 语言就没有这种自动内存管理。
5. 三大特性之封装
数据隐藏
面向对象编程的一个关键部分是封装,它涉及将相关的变量和函数打包到一个简单易用的对象中 - 一个类的实例。
一个相关的概念是数据隐藏,它指出一个类的实现细节应该被隐藏,并且为那些想要使用这个类的用户提供一个干净的标准接口。
在其他编程语言中,这通常使用私有方法和属性来完成,这些私有方法和属性阻止对类中某些方法和属性被外部访问。
Python 略有不同。没有对任何一个阶级的部分进行任意的限制。因此,没有办法强制一个方法或属性是严格私密的。
但是,有些方法可以阻止人们访问某个类的某些部分。
弱私有
弱的私有方法和属性在开头只有一个下划线。
这表示它们是私有的,不应该被外部代码使用。但是,它大多只是一个约定,并不会阻止外部代码访问它们。
它唯一的实际效果是 from 模块名 import * 不会导入以单个下划线开头的变量。
class Queue:
def __init__(self, contents):
self._hiddenlist = list(contents)
def push(self, value):
self._hiddenlist.insert(0, value)
def pop(self):
return self._hiddenlist.pop(-1)
def __repr__(self):
return "Queue({})".format(self._hiddenlist)
queue = Queue([1, 2, 3])
print(queue)
queue.push(0)
print(queue)
queue.pop()
print(queue)
print(queue._hiddenlist)
"""
Queue([1, 2, 3])
Queue([0, 1, 2, 3])
Queue([0, 1, 2])
[0, 1, 2]
"""
在上面的代码中,_hiddenlist 属性被标记为私有的,但仍然可以在外部代码中访问。
__repr__
魔术方法用于实例的字符串表示。
强私有
强私有方法和属性在名称的开始处有一个双下划线。这导致他们的名字错位,这意味着他们不能从类外访问。
这样做的目的不是确保它们保持私有,而是为了避免错误,如果存在具有相同名称的方法或属性的子类时。
名称 错位 方法仍然可以在外部访问,但是以不同的名称访问。 Spam 类的 __private 方法可以通过 _Spam__private 方法外部访问。
class Spam:
__egg = 7
def print_egg(self):
print(self.__egg)
s = Spam()
s.print_egg()
print(s._Spam__egg)
print(s.__egg)
"""
7
7
AttributeError: 'Spam' object has no attribute '__egg'
"""
基本上,Python 通过内部更改名称来包含类名来保护这些成员。
6. 类方法和静态方法
类方法
到目前为止,我们所看到的对象的方法被一个类的实例所调用,然后被传递给方法的 self 参数。
类方法是不同的 - 它们被一个类所调用,类方法传递的 参数是 cls 。
类方法用 classmethod 装饰器标记。
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
@classmethod
def new_square(cls, side_length):
return cls(side_length, side_length)
square = Rectangle.new_square(5)
print(square.calculate_area())
# result: 25
new_square 是一个类方法,在类上调用,而不是在类的实例上调用。它返回类 cls 的新对象。
从技术上说,参数 self 和 cls 只是惯例; 他们可以改变为其他任何东西。然而,它们是普遍被遵循的,所以坚持使用它们是明智的。
静态方法
静态方法与类方法类似,只是它们没有任何额外的参数。
它们用 staticmethod 装饰器标记。
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@staticmethod
def validate_topping(topping):
if topping == "pineapple":
raise ValueError("No pineapples!")
else:
return True
ingredients = ["cheese", "onions", "spam"]
if all(Pizza.validate_topping(i) for i in ingredients):
pizza = Pizza(ingredients)
除了可以从类的一个实例调用它们之外,静态方法的行为与纯函数类似。
7. 类 - 属性
属性提供了一种自定义实例属性访问的方法。
它们是通过将属性装饰器放在一个方法上面创建的,这意味着当访问与方法同名的实例属性时,方法将被调用。
属性的一种常见用法是使属性为只读。
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@property
def pineapple_allowed(self):
return False
pizza = Pizza(["cheese", "tomato"])
print(pizza.pineapple_allowed)
pizza.pineapple_allowed = True
"""
False
AttributeError: can't set attribute
"""
属性的设置与获取
属性也可以通过定义 setter/getter
函数来设置。 setter
函数设置相应的属性值。 getter
函数获取相应的属性值。 要定义一个 setter,你需要使用一个与属性相同名字的装饰器,后面跟着 .setter
。 这同样适用于定义 getter
函数。
8. 实操
在管理不同的对象及其关系时,面向对象是非常有用的。当你开发具有不同角色和特征的游戏时,这是特别有用的。
我们来看一个示例项目,该项目展示了如何在游戏开发中使用类。
要开发的游戏是一个老式的基于文本的冒险游戏。
下面是函数处理输入和简单的解析。
- 定义一个游戏输入,对输入简单解析并做出反应
- 为游戏对象添加查看状态的方法
- 为Goblin类添加更详细的信息
"""
我们创建了Goblin类,它继承自GameObjects类
我们还创建了一个新的函数examine,它返回对象的描述
现在我们可以添加一个新的“examine”动词到我们的字典
"""
class GameObject:
class_name = ""
desc = ""
objects = {}
def __init__(self, name):
self.name = name
GameObject.objects[self.class_name] = self
def get_desc(self):
return self.class_name + "\n" + self.desc
class Goblin(GameObject):
def __init__(self, name):
self.class_name = "goblin"
self.health = 3
self._desc = " A foul creature"
super().__init__(name)
@property
def desc(self):
if self.health >=3:
return self._desc
elif self.health == 2:
health_line = "It has a wound on its knee."
elif self.health == 1:
health_line = "Its left arm has been cut off!"
elif self.health <= 0:
health_line = "It is dead."
return self._desc + "\n" + health_line
@desc.setter
def desc(self, value):
self._desc = value
goblin = Goblin("Gobbly")
def examine(noun):
if noun in GameObject.objects:
return GameObject.objects[noun].get_desc()
else:
return "There is no {} here.".format(noun)
def get_input():
command = input(": ").split()
verb_word = command[0]
if verb_word in verb_dict:
verb = verb_dict[verb_word]
else:
print("Unknown verb {}". format(verb_word))
return
if len(command) >= 2:
noun_word = command[1]
print (verb(noun_word))
else:
print(verb("nothing"))
def say(noun):
return 'You said "{}"'.format(noun)
def hit(noun):
if noun in GameObject.objects:
thing = GameObject.objects[noun]
if type(thing) == Goblin:
thing.health = thing.health - 1
if thing.health <= 0:
msg = "You killed the goblin!"
else:
msg = "You hit the {}".format(thing.class_name)
else:
msg ="There is no {} here.".format(noun)
return msg
verb_dict = {
"say": say,
"examine": examine,
}
while True:
get_input()
"""
: say Hello!
You said "Hello!"
: say Goodbye!
You said "Goodbye!"
: test
Unknown verb test
"""
上面的代码从用户处获取输入,并尝试将第一个单词与 verb_dict 中的命令进行匹配。如果找到匹配,则调用相应的功能。
第三章正则表达式
1. 正则表达式的定义
正则表达式是各种字符串操作的强大工具。
正则表达式是一种特定于领域的语言(DSL),作为大多数现代编程语言的库而不仅仅是 Python。
正则表达式对于以下两种主要任务是很有用的:
- 验证字符串是否匹配模式(例如,字符串具有电子邮件地址的格式)
- 对字符串中进行替换(如将所有美式拼写改为英式拼写)。
正则表达式是一个高度专业化的迷你编程语言,与 SQL(用于数据库操作)类似适用于多种编程环境。
match()
Python 中的正则表达式可以使用 re 模块来访问,它是标准库的一部分。
定义正则表达式之后,可以使用 re.match 函数来确定字符串的开头是否匹配。
如果匹配,match 返回表示匹配的对象,否则返回 None。
为了避免在处理正则表达式时出现混淆,我们使用原始字符串 r"expression"。
原始字符串不会转义任何东西,这使得使用正则表达式更容易。
import re
pattern = r"spam"
if re.match(pattern, "spamspamspam"):
print("Match")
else:
print("No match")
# result: Match
上面的例子检查字符串是否匹配 "spam",如果是,则打印 "Match"。
search()和findall()
其他的正则函数是 re.search 和 re.findall。
re.search 函数在字符串中的任何位置找到匹配的模式。
re.findall 函数返回一个与模式匹配的所有子串的列表。
import re
pattern = r"spam"
if re.match(pattern, "eggspamsausagespam"):
print("Match")
else:
print("No match")
if re.search(pattern, "eggspamsausagespam"):
print("Match")
else:
print("No match")
print(re.findall(pattern, "eggspamsausagespam"))
"""
No match
Match
['spam', 'spam']
"""
函数 re.finditer 和 re.findall 类似,不过它返回一个迭代器,而不是一个列表。
索引
正则表达式搜索使用多个方法返回一个对象,提供有关它的详细信息。
这些方法包括返回匹配的字符串的组,返回第一个匹配的开始和结束位置的开始和结束,以及将第一个匹配的开始和结束位置作为元组返回的跨度。
import re
pattern = r"pam"
match = re.search(pattern, "eggspamsausage")
if match:
print(match.group())
print(match.start())
print(match.end())
print(match.span())
"""
pam
4
7
(4, 7)
"""
搜索和替换sub()
re.sub(pattern, repl, string, max=0) 此方法用 repl 替换string中所有出现的pattern,max可以限定修改数量。
import re
str = "My name is Loen. Hi Loen."
pattern = r"Loen"
newstr = re.sub(pattern, "Amy", str)
print(newstr)
# result: My name is Amy. Hi Amy.
2. re模块之元字符
元字符使正则表达式比普通的字符串方法更强大。
元字符允许您创建正则表达式来表示像“一个或多个元音的重复”这样的概念。
如果要创建与文字元字符(例如“$”)匹配的正则表达式,则元字符的存在会造成问题。你可以通过在它们前面加一个反斜杠来转义元字符。
但是,这可能会导致问题,因为反斜杠在正常的 Python 字符串中也有一个转义的功能。
这可能意味着需要连续放置三或四个反斜杠来完成所有的转义。
元字符.
我们将看到的第一个元字符是 . (点)。
它匹配任何字符,但不匹配新的行。
import re
pattern = r"gr.y"
if re.match(pattern, "grey"):
print("Match 1")
if re.match(pattern, "gray"):
print("Match 2")
if re.match(pattern, "blue"):
print("Match 3")
"""
Match 1
Match 2
"""
元字符^&
接下来的两个元字符是 ^ 和 $ 。
这两个分别匹配字符串的开始和结束。
import re
pattern = r"^gr.y$"
if re.match(pattern, "grey"):
print("Match 1")
if re.match(pattern, "gray"):
print("Match 2")
if re.match(pattern, "stingray"):
print("Match 3")
"""
Match 1
Match 2
"""
模式 “^gr.y$” 表示字符串应该以gr开头,然后跟随一个任何字符,除了换行符,并以y结尾。
元字符*
元字符 * 表示 “零次或者多次重复以前的事情”。它匹配尽可能多的重复。* 号前可以是一个单独的字符,一个类,或一组括在括号中的字符。
import re
pattern = r"egg(spam)*"
if re.match(pattern, "egg"):
print("Match 1")
if re.match(pattern, "eggspamspamegg"):
print("Match 2")
if re.match(pattern, "spam"):
print("Match 3")
"""
Match 1
Match 2
"""
上面的例子匹配以 "egg" 开头的字符串,并跟随零个或多个 "spam"。
元字符+
元字符 + 与 * 非常相似,不同之处在于 + 是 “一个或多个重复”,而 * 是“零个或多个重复”。
import re
pattern = r"g+"
if re.match(pattern, "g"):
print("Match 1")
if re.match(pattern, "gggggggggggggg"):
print("Match 2")
if re.match(pattern, "abc"):
print("Match 3")
"""
Match 1
Match 2
"""
总结: *匹配0个或更多的前面的表达式。 +匹配1个或更多的前面的表达式。
元字符?
元字符 ? 匹配 “零重复或一次重复”。
import re
pattern = r"ice(-)?cream"
if re.match(pattern, "ice-cream"):
print("Match 1")
if re.match(pattern, "icecream"):
print("Match 2")
if re.match(pattern, "sausages"):
print("Match 3")
if re.match(pattern, "ice--ice"):
print("Match 4")
"""
Match 1
Match 2
"""
元字符{}
大括号可以用来表示两个数字之间的重复次数。
正则表达式 {x,y} 表示 “在x和y之间重复某事”。
因此 {0, 1} 与 ? 相同。
大括号如果第一个数字缺失,则将其视为零。如果第二个数字丢失,则被认为是无限的。
import re
pattern = r"9{1,3}$"
if re.match(pattern, "9"):
print("Match 1")
if re.match(pattern, "999"):
print("Match 2")
if re.match(pattern, "9999"):
print("Match 3")
"""
Match 1
Match 2
"""
"9{1,3}$" 匹配具有1到3个9的字符串。
3. re模块之字符类
字符类提供了一种只匹配特定字符集中的一个的方法。
通过将匹配的字符放在方括号内来创建字符类。
import re
pattern = r"[aeiou]"
if re.search(pattern, "grey"):
print("Match 1")
if re.search(pattern, "qwertyuiop"):
print("Match 2")
if re.search(pattern, "rhythm myths"):
print("Match 3")
"""
Match 1
Match 2
"""
搜索函数中的模式 [aeiou] 匹配包含任何一个定义的字符的所有字符串。
元字符[-]
字符类也可以匹配字符的范围。
一些例子:
[a-z] 匹配任何小写字母字符。
[G-P] 匹配从 G 到 P 的任何大写字符。
[0-9] 匹配任何数字。
一个字符类可以包含多个范围。例如,[A-Za-z] 匹配任何一个字母。
import re
pattern = r"[A-Z][A-Z][0-9]"
if re.search(pattern, "LS8"):
print("Match 1")
if re.search(pattern, "E3"):
print("Match 2")
if re.search(pattern, "1ab"):
print("Match 3")
"""
Match 1
"""
上例中的模式匹配包含两个大写字母后跟一个数字的字符串。
元字符^
在字符类的开头放置一个 ^ 来反转它, 这使得它匹配除包含的字符之外的任何字符。
其他元字符(如 $ 和 .)在字符类中没有意义。
元字符 ^ 没有意义,除非它是一个字符类中的第一个字符。
import re
pattern = r"[^A-Z]"
if re.search(pattern, "this is all quiet"):
print("Match 1")
if re.search(pattern, "AbCdEfG123"):
print("Match 2")
if re.search(pattern, "THISISALLSHOUTING"):
print("Match 3")
"""
Match 1
Match 2
"""
模式[^A-Z]匹配不包括大写字母的所有字符。
4. re模块之分值方式
元字符()
可以通过用圆括号围绕正则表达式的一部分来创建组。
这意味着一个组可以作为元字符的参数,如 * 和?。
import re
pattern = r"egg(spam)*"
if re.match(pattern, "egg"):
print("Match 1")
if re.match(pattern, "eggspamspamspamegg"):
print("Match 2")
if re.match(pattern, "spam"):
print("Match 3")
"""
Match 1
Match 2
"""
(spam)代表上面示例模式中的一个组。
group()
可以使用组功能访问匹配组中的内容。
可以调用 group(0) 或者 group() 返回整个匹配。
调用 group(n) ,n 要大于 0,返回匹配的第 n 个组。
groups() 返回所有匹配的分组。
import re
pattern = r"a(bc)(de)(f(g)h)i"
match = re.match(pattern, "abcdefghijklmnop")
if match:
print(match.group())
print(match.group(0))
print(match.group(1))
print(match.group(2))
print(match.groups())
"""
abcdefghi
abcdefghi
bc
de
('bc', 'de', 'fgh', 'g')
"""
从上面的例子可以看出,组可以嵌套。
组的命名
有几种特殊的群组。
两个有用的命名组和非捕获组。
命名组格式为 (?P<name>...)
,其中 name 是组的名称,...是内容。他们的表现与正常组完全相同,除了可以通过 group(name) 代替编号访问。
非捕获组格式为 (?:...)
。它们不能通过组方法访问,所以可以将它们添加到现有的正则表达式中而不会破坏编号。
import re
pattern = r"(?P<first>abc)(?:def)(ghi)"
match = re.match(pattern, "abcdefghi")
if match:
print(match.group("first"))
print(match.groups())
"""
abc
('abc', 'ghi')
"""
元字符|
另一个重要的元字符是 | 。
意思是与,比如 red|blue 匹配 "red"或者"blue"。
import re
pattern = r"gr(a|e)y"
match = re.match(pattern, "gray")
if match:
print ("Match 1")
match = re.match(pattern, "grey")
if match:
print ("Match 2")
match = re.match(pattern, "griy")
if match:
print ("Match 3")
"""
Match 1
Match 2
"""
5. re模块之特殊序列
\1匹配
在正则表达式中可以使用各种特殊的序列。它们写成反斜杠,然后是一个字符。
一个有用的特殊序列是反斜杠和1到99之间的数字,例如 \1 或 \17。这匹配该数字的组的表达式。
import re
pattern = r"(.+) \1"
match = re.match(pattern, "word word")
if match:
print ("Match 1")
match = re.match(pattern, "?! ?!")
if match:
print ("Match 2")
match = re.match(pattern, "abc cde")
if match:
print ("Match 3")
"""
Match 1
Match 2
"""
请注意, "(.+) \1" 与 "(.+) (.+)" 不一样,因为 \1 是指第一组的子表达式,它是匹配的表达式本身。 Q:哪些可以被 (abc|xyz)\1 匹配? A:"abcabc","xyzxyz" WA:"abcxyz"因为\1匹配了第一组,因此第一组匹配的是abc所以\1也必须是abc
\d,\s,\w
更有用的特殊序列是 \d,\s和 \w。
这些分别匹配数字,空格和单词字符。
在 ASCII 模式下,它们相当于 [0-9],[\t \n \r \f \v] 和 [a-zA-Z0-9_]。
在 Unicode 模式下,它们也匹配某些其他字符,例如,\w 匹配带有重音的字母。
大写字母 - \D,\S 和 \W - 这些特殊序列的版本意味着与小写字母相反的版本,例如,\D 匹配任何不是数字的东西。
import re
pattern = r"(\D+\d)"
match = re.match(pattern, "Hi 999!")
if match:
print("Match 1")
match = re.match(pattern, "1, 23, 456!")
if match:
print("Match 2")
match = re.match(pattern, " ! $?")
if match:
print("Match 3")
# result: Match 1
(\D+\d) 匹配一个或多个非数字后跟一个数字。
\A,\Z,\b
其他特殊序列是 \A,\Z和 \b。
序列 \A 和 \Z 分别匹配字符串的开头和结尾。
序列 \b 匹配 \w 和 \W 字符之间的空字符串,或 \w 字符和字符串的开始或结尾。非正式地,它代表了单词之间的界限。
序列 \B 匹配其他地方的空字符串。
import re
pattern = r"\b(cat)\b"
match = re.search(pattern, "The cat sat!")
if match:
print ("Match 1")
match = re.search(pattern, "We s>cat<tered?")
if match:
print ("Match 2")
match = re.search(pattern, "We scattered.")
if match:
print ("Match 3")
"""
Match 1
Match 2
"""
"\b(cat)\b" 基本上与单词边界包围的单词 "cat" 匹配。
6. 邮箱读取
import re
pattern = r"([\w\.-]+)@([\w\.-]+)(\.[\w\.]+)"
str = "Please contact 321321@163.com for assistance"
match = re.search(pattern, str)
if match:
print(match.group())
# result:321321@163.com
第四章代码优化
1. 函数参数
*arg
Python 允许具有不同数量的参数的函数。
使用 *args 作为函数参数,可以将任意数量的参数传递给该函数。参数可以作为函数主体中的元组参数访问。
def function(named_arg, *args):
print(named_arg)
print(args)
function(1, 2, 3, 4, 5)
"""
1
(2, 3, 4, 5)
"""
参数 *args 必须位于命名参数之后。 名字 args 只是一个惯例,你可以选择使用另一个自定义的名字。
默认值
给函数指定的参数可以通过给它们一个默认值使其成为可选的参数。
指定默认值的参数,必须在未指定默认值参数之后。
def function(x, y, fruit="apple"):
print(fruit)
function(1, 2)
function(3, 4, "banana")
"""
apple
banana
"""
在传入参数的情况下,默认值将被忽略。 如果参数未传入,则使用默认值。
**args
**kwargs(代表关键字参数)允许你处理尚未预先定义的命名参数。
关键字参数返回一个字典,其中键是参数名称,值是参数数值。
def my_func(x, y=7, *args, **kwargs):
print(kwargs)
my_func(2, 3, 4, 5, 6, a=7, b=8)
"""
{'a': 7, 'b': 8}
"""
a 和 b 是我们传递给函数调用的参数的名称。 **kwargs 返回的参数不包含在 *args 中。
2. 元组解包
元组解包允许你将一个可迭代的元素(通常是一个元组)分配给变量。
numbers = (1, 2, 3)
a, b, c = numbers
print(a)
print(b)
print(c)
"""
1
2
3
"""
交换变量的值可以通过 a, b = b, a,因为 b, a 在右边形成元组(b, a),然后解包。
星号*
以星号(*)开头的变量将从迭代中取出所有其他变量剩余的值。
a, b, *c, d = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(a)
print(b)
print(c)
print(d)
"""
1
2
[3, 4, 5, 6, 7, 8]
9
"""
3. 三元运算符
三元运算符提供if语句的功能,同时使用较少的代码。
三元运算符不应该被过度使用,因为它可以轻易地降低可读性,但是在分配变量时它们通常是有用的。
a = 7
b = 1 if a >= 5 else 42
print(b)
# result: 1
三元运算符检查条件并返回相应的值。在上面的例子中,如果条件成立,则 b 被赋值为 1。如果 a 小于 5,则将被分配 42。
status = 1
msg = "注销" if status == 1 else "登录"
三元运算符与大多数运算符不同,它有三个参数。
4.else语句
else 语句与 if 语句一起使用最为普遍,但它也可以遵循 for 或 while 循环,从而赋予它不同的含义。
使用 for 或 while 循环,如果循环正常结束(当 break 语句不会导致循环退出时),则会调用其中的代码。
for i in range(10):
if i == 999:
break
else:
print("Unbroken 1")
for i in range(10):
if i == 5:
break
else:
print("Unbroken 2")
# result: Unbroken 1
第一个 for 循环正常执行,导致打印 “Unbroken 1”。 第二个循环由于中断而退出,这就是为什么 else 语句不被执行。
try/expect
else 语句也可以和 try/except 语句一起使用。
在这种情况下,只有在 try 语句中没有发生错误时才会执行其中的代码。
try:
print(1)
except ZeroDivisionError:
print(2)
else:
print(3)
try:
print(1/0)
except ZeroDivisionError:
print(4)
else:
print(5)
"""
1
3
4
"""
主函数__main__
大多数 Python 代码是要导入的模块,或者是执行某些操作的脚本。
但是,有时将一个文件既作为模块导入也作为脚本运行是非常有用的。
为此,将脚本代码放置在 if __name__ == "__main__"
中。
这确保了单独运行文件会被允许,如果文件被导入,它将不会运行。
def function():
print("This is a module function")
if __name__=="__main__":
print("This is a script")
# result: This is a script
当 Python 解释器读取源文件时,它会执行它在文件中找到的所有代码。在执行代码之前,它定义了一些特殊的变量。 例如,如果 Python 解释器正在将该模块(源文件)作为主程序运行,则会将特殊的
__name__
变量设置为"__main__"
。
如果这个文件是从另一个模块导入的,__name__
将被设置为模块的名字。
第五章打包
安装pyinstaller
pip install pyinstaller
在当前目录下打包python文件
pyinstaller setup.py
(venv) PS C:\Users\pythonProject> pyinstaller project1.py