TL;DR

Way2AI系列,确保出发去"改变世界"之前,我们已经打下了一个坚实的基础。

本文简单介绍了Python这门语言的必备知识。

Variables

变量是用于存储数据的容器,它们由名称和值定义。
在我们的示例中,我们使用变量名x,但是当您处理特定任务时,请确保在创建变量(函数、类等)时指定明确含义的名称(例如first_name),以确保程序的可读性。

1
2
3
4
5
6
7
8
# integer variable
x = 5
print (x)
print (type(x))

# Output
# 5
# <class 'int'>

我们可以通过将新值赋给变量来改变它的值。

1
2
3
4
5
6
# sring variable
x = "hello"
print (x, type(x))

# Output
# hello <class 'str'>

python有许多不同类型的变量:integers, floats, strings, boolean 等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# int variable
x = 5
print (x, type(x))

# Output
# 5 <class 'int'>


# float variable
x = 5.0
print (x, type(x))

# Output
# 5.0 <class 'float'>


# string variable
x = "5"
print (x, type(x))

# Output
# 5 <class 'str'>


# boolean variable
x = True
print (x, type(x))

# Output
# True <class 'bool'>

我们也可以使用变量进行运算操作:

1
2
3
4
5
6
7
8
9
# Variables can be used with each other
a = 1
b = 2
c = a + b
print (c)

# Output
# 3

我们应该始终知道我们正在处理哪种类型的变量,以便可以正确地对它们进行操作。

Lists

列表是一个有序的、可变的值集合,由方括号包围并用逗号分隔。一个列表可以包含许多不同类型的变量。下面是一个包含整数、字符串和浮点数的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Creating a list
x = [3, "hello", 1.2]
print (x)

# Output
# [3, 'hello', 1.2]


# Length of a list
len(x)

# Output
# 3

使用append方法向列表里添加元素

1
2
3
4
5
6
7

# Adding to a list
x.append(7)
print (x, len(x))

# Output
# [3, 'hello', 1.2, 7] 4

通过indexing修改已存在的元素

1
2
3
4
5
6
7
# Replacing items in a list
x[1] = "bye"
print (x)

# Output
# [3, 'bye', 1.2, 7]

列表也可以进行操作

1
2
3
4
5
6
7
# Operations
y = [2.4, "world"]
z = x + y
print (z)

# Output
# [3, 'bye', 1.2, 7, 2.4, 'world']

Tuples

元组是有序且不可变(无法更改)的集合。我们将使用元组来存储永远不会更改的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Creating a tuple
x = (3.0, "hello") # tuples start and end with ()
print (x)

# Output
# (3.0, 'hello')

# Adding values to a tuple
x = x + (5.6, 4)
print (x)


# Output
# (3.0, 'hello', 5.6, 4)

# Try to change (it won't work and we get an error)
x[0] = 1.2

# Output
# TypeError: 'tuple' object does not support item assignment

Sets

集合是无序且可变的。而且,集合中的每个元素必须是唯一的。

1
2
3
4
5
6
7
8
# Sets
text = "Learn ML with Made With ML"
print (set(text))
print (set(text.split(" ")))

# Output
# {'e', 'M', ' ', "r", "w", 'd', 'a', 'h', 't', 'i', 'L', 'n', "w"}
# {'with', 'Learn', 'ML', 'Made', 'With'}

Indexing

从列表中进行索引和切片可以让我们检索到特定的值。请注意,索引可以是正数(从0开始)或负数(-1及以下,其中-1是列表中的最后一项)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Indexing
x = [3, "hello", 1.2]
print ("x[0]: ", x[0])
print ("x[1]: ", x[1])
print ("x[-1]: ", x[-1]) # the last item
print ("x[-2]: ", x[-2]) # the second to last item

# Output
# x[0]: 3
# x[1]: hello
# x[-1]: 1.2
# x[-2]: hello


# Slicing
print ("x[:]: ", x[:]) # all indices
print ("x[1:]: ", x[1:]) # index 1 to the end of the list
print ("x[1:2]: ", x[1:2]) # index 1 to index 2 (not including index 2)
print ("x[:-1]: ", x[:-1]) # index 0 to last index (not including last index)

# Output
# x[:]: [3, 'hello', 1.2
# x[1:]: ['hello', 1.2]
# x[1:2]: ['hello']
# x[:-1]: [3, 'hello']

Dictionaries

字典是一种无序的可变键值对集合。您可以根据键检索值,而且一个字典不能有两个相同的键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Creating a dictionary
person = {"name": "Goku",
"eye_color": "brown"}
print (person)
print (person["name"])
print (person["eye_color"])

# Output
# {"name": "Goku", "eye_color": "brown"}
# Goku
# brown


# Adding new key-value pairs
person["age"] = 24
print (person)

# Output
# {"name": "Goku", "eye_color": "green", "age": 24}


# Length of a dictionary
print (len(person))

# Output
# 3

当然,使用Python几乎可以实现任何事情。例如,尽管原生字典是无序的,但我们可以利用OrderedDict数据结构来改变它(如果我们想按特定顺序迭代键等,则非常有用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from collections import OrderedDict

# Native dict
d = {}
d["a"] = 2
d["c"] = 3
d["b"] = 1
print (d)

# Output
# {'a': 2, 'c': 3, 'b': 1}

# Python3.7以后,原生的字典是是按插入的顺序排序的。


# Dictionary items
print (d.items())

# Output
# dict_items([('a', 2), ('c', 3), ('b', 1)])


# Order by keys
print (OrderedDict(sorted(d.items())))

# Output
# OrderedDict([('a', 2), ('b', 1), ('c', 3)])


# Order by values
print (OrderedDict(sorted(d.items(), key=lambda x: x[1])))

# Output
# OrderedDict([('b', 1), ('a', 2), ('c', 3)])

If statements

我们可以使用if语句来有条件地执行某些操作。条件由if、elif(代表else if)和else这几个关键字定义。我们可以拥有任意多个elif语句。每个条件下面缩进的代码是在该条件为True时将要执行的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# If statement
x = 4
if x < 1:
score = "low"
elif x <= 4: # elif = else if
score = "medium"
else:
score = "high"
print (score)

# Output
# medium


# If statement with a boolean
x = True
if x:
print ("it worked")

# Output
# it worked

Loops

For loops

for循环可以迭代值的集合(列表、元组、字典等)。缩进的代码对于集合中的每个项目都会执行。

1
2
3
4
5
6
7
8
9
# For loop
veggies = ["carrots", "broccoli", "beans"]
for veggie in veggies:
print (veggie)

# Output
# carrots
# broccoli
# beans

当循环遇到break命令时,循环将立即终止。如果列表中还有更多的项,则它们不会被处理。

1
2
3
4
5
6
7
8
9
# `break` from a for loop
veggies = ["carrots", "broccoli", "beans"]
for veggie in veggies:
if veggie == "broccoli":
break
print (veggie)

# Output
# carrots

当循环遇到 continue 命令时,该循环将仅跳过列表中当前项的所有其他操作。如果列表中还有更多项,则循环将正常继续。

1
2
3
4
5
6
7
8
9
10
# `continue` to the next iteration
veggies = ["carrots", "broccoli", "beans"]
for veggie in veggies:
if veggie == "broccoli":
continue
print (veggie)

# Output
# carrots
# beans

While Loops

当条件为真时,while循环可以重复执行。我们也可以在while循环中使用continue和break命令。

1
2
3
4
5
6
7
8
9
10
# While loop
x = 3
while x > 0:
x -= 1 # same as x = x - 1
print (x)

# Output
# 2
# 1
# 0

List comprehension

我们可以结合列表和for循环的知识,利用列表推导式来创建简洁的代码。

1
2
3
4
5
6
7
8
9
10
# For loop
x = [1, 2, 3, 4, 5]
y = []
for item in x:
if item > 2:
y.append(item)
print (y)

# Output
# [3, 4, 5]

1
2
3
4
5
6
# List comprehension
y = [item for item in x if item > 2]
print (y)

# Output
# [3, 4, 5]

下面示例一个复杂的列表推导式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Nested for loops
words = [["Am", "ate", "ATOM", "apple"], ["bE", "boy", "ball", "bloom"]]
small_words = []
for letter_list in words:
for word in letter_list:
if len(word) < 3:
small_words.append(word.lower())
print (small_words)

# Output
# ['am', 'be']


# Nested list comprehension
small_words = [word.lower() for letter_list in words for word in letter_list if len(word) < 3]
print (small_words)

# Output
# ['am', 'be']

Functions

函数是一种将可重用代码模块化的方式。它们由关键字def定义,代表definition,可以包含以下组件。

1
2
3
4
5
# Define the function
def add_two(x):
"""Increase x by 2."""
x += 2
return x

以下是使用该函数时可能需要的组件。我们需要确保函数名称和输入参数与上面定义的函数匹配。

1
2
3
4
5
6
7
# Use the function
score = 0
new_score = add_two(x=score)
print (new_score)

# Output
# 2

一个函数可以有任意多个输入参数和输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Function with multiple inputs
def join_name(first_name, last_name):
"""Combine first name and last name."""
joined_name = first_name + " " + last_name
return joined_name


# Use the function
first_name = "Goku"
last_name = "Mohandas"
joined_name = join_name(
first_name=first_name, last_name=last_name)
print (joined_name)

# Output
# Goku Mohandas

在使用函数时,始终使用关键字参数是一个好习惯,这样非常清楚每个输入变量属于哪个函数输入参数。相关的是,您经常会看到术语*args和**kwargs,它们代表位置参数和关键字参数。当它们传递到函数中时,可以提取它们。星号的重要性在于任何数量的位置参数和关键字参数都可以传递到函数中。

1
2
3
4
5
6
7
8
9
10
def f(*args, **kwargs):
x = args[0]
y = kwargs.get("y")
print (f"x: {x}, y: {y}")


f(5, y=2)

# Output
# x: 5, y: 2

Classes

类是对象构造函数,也是Python面向对象编程的基本组成部分。它们由一组定义类及其操作的函数组成。

Magic methods

可以使用像__init____str__这样的魔术方法来自定义类,以实现强大的操作。这些也被称为dunder方法,表示有两个前置和后置下划线。

__init__方法用于初始化类的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Creating the class
class Pet(object):
"""Class object for a pet."""

def __init__(self, species, name):
"""Initialize a Pet."""
self.species = species
self.name = name


# Creating an instance of a class
my_dog = Pet(species="dog",
name="Scooby")
print (my_dog)
print (my_dog.name)

# Output
# <__main__.Pet object at 0x7fe487e9c358>
# Scooby

print(my_dog)命令打印了一些与我们不太相关的内容。让我们使用__str__函数来修复它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Creating the class
# Creating the class
class Pet(object):
"""Class object for a pet."""

def __init__(self, species, name):
"""Initialize a Pet."""
self.species = species
self.name = name

def __str__(self):
"""Output when printing an instance of a Pet."""
return f"{self.species} named {self.name}"


# Creating an instance of a class
my_dog = Pet(species="dog",
name="Scooby")
print (my_dog)
print (my_dog.name)

# Output
# dog named Scooby
# Scooby

Object functions

除了这些魔术方法,类还可以拥有对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Creating the class
class Pet(object):
"""Class object for a pet."""

def __init__(self, species, name):
"""Initialize a Pet."""
self.species = species
self.name = name

def __str__(self):
"""Output when printing an instance of a Pet."""
return f"{self.species} named {self.name}"

def change_name(self, new_name):
"""Change the name of your Pet."""
self.name = new_name


# Creating an instance of a class
my_dog = Pet(species="dog", name="Scooby")
print (my_dog)
print (my_dog.name)

# Output
# dog named Scooby
# Scooby


# Using a class's function
my_dog.change_name(new_name="Scrappy")
print (my_dog)
print (my_dog.name)

# Output
# dog named Scrappy
# Scrappy

Inheritance

我们还可以使用继承来构建类的层次结构,这样我们就能够从另一个类(父类)中继承所有属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Dog(Pet):
def __init__(self, name, breed):
super().__init__(species="dog", name=name)
self.breed = breed

def __str__(self):
return f"A {self.breed} doggo named {self.name}"


scooby = Dog(species="dog", breed="Great Dane", name="Scooby")
print (scooby)

# Output
# A Great Dane doggo named Scooby


scooby.change_name("Scooby Doo")
print (scooby)

# Output
# A Great Dane doggo named Scooby Doo

注意我们从父类Pet继承了初始化变量,如species和name。我们还继承了像change_name()这样的函数。

Methods

在类方面,有两个重要的装饰器方法需要了解:@classmethod 和 @staticmethod。我们将在下一节中学习装饰器,但这些特定的方法与类相关,因此我们将在此处介绍它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Dog(Pet):
def __init__(self, name, breed):
super().__init__(species="dog", name=name)
self.breed = breed

def __str__(self):
return f"{self.breed} named {self.name}"

@classmethod
def from_dict(cls, d):
return cls(name=d["name"], breed=d["breed"])

@staticmethod
def is_cute(breed):
return True # all animals are cute!

@classmethod允许我们通过传递未实例化的类本身(cls)来创建类实例。这是从对象(例如字典)创建(或加载)类的好方法。

1
2
3
4
5
6
7
# Create instance
d = {"name": "Cassie", "breed": "Border Collie"}
cassie = Dog.from_dict(d=d)
print(cassie)

# Output
# Border Collie named Cassie

一个 @staticmethod 可以从未实例化的类对象中调用,因此我们可以这样做:

1
2
3
4
5
6
# Static method
res = Dog.is_cute(breed="Border Collie")
print (res)

# Output
# True

Decorators

回想一下,函数允许我们将代码模块化并重复使用。然而,我们经常希望在主要函数执行之前或之后添加一些功能,并且可能希望对许多不同的函数进行此操作。我们可以使用装饰器来实现这个目的,而不是向原始函数添加更多代码!

装饰器:在函数之前/之后增加处理以增强该函数。装饰器包裹在主函数周围,允许我们对输入和/输出进行操作。

假设我们有一个名为operations的函数,它将输入值x增加1。

1
2
3
4
5
6
7
8
9
10
def operations(x):
"""Basic operations."""
x += 1
return x


operations(x=1)

# Output
# 2

现在假设我们想要在操作函数执行前后将输入 x 增加 1,为了说明这个例子,让我们假设增量必须是单独的步骤。

下面是我们通过更改原始代码来完成的方案:

1
2
3
4
5
6
7
8
9
10
11
12
def operations(x):
"""Basic operations."""
x += 1
x += 1
x += 1
return x


operations(x=1)

# Output
# 4

我们已经实现了想要的功能,但是我们现在增加了操作函数的规模,如果我们想对任何其他函数进行相同的递增操作,那么我们也必须将相同的代码添加到所有这些函数中… 这不太高效。为了解决这个问题,让我们创建一个名为add的装饰器,在主函数f执行之前和之后将x增加1。

Creating a decorator

装饰器函数接受一个函数f作为参数,这个函数是我们想要包装的函数,在本例中,它是operations()。装饰器的输出是其包装器函数,该函数接收传递给函数f的参数和关键字参数。

在包装函数中,我们可以:

    1. 提取传递给函数f的输入参数
    1. 对函数输入进行任何更改
    1. 执行函数f
    1. 对函数输出进行任何更改
    1. wrapper函数返回一些值,这就是装饰器返回的值,因为它也返回wrapper。
1
2
3
4
5
6
7
8
9
10
# Decorator
def add(f):
def wrapper(*args, **kwargs):
"""Wrapper function for @add."""
x = kwargs.pop("x") # .get() if not altering x
x += 1 # executes before function f
x = f(*args, **kwargs, x=x)
x += 1 # executes after function f
return x
return wrapper

我们可以通过在主函数顶部添加 @ 符号来简单地使用这个装饰器。

1
2
3
4
5
6
7
8
9
10
11
@add
def operations(x):
"""Basic operations."""
x += 1
return x


operations(x=1)

# Output
# 4

假设我们想要调试并查看实际执行operations()函数的操作。

1
2
3
4
operations.__name__, operations.__doc__

# Output
# ('wrapper', 'Wrapper function for @add.')

函数名和文档字符串并不是我们要寻找的内容,但它们出现在这里是因为执行的是包装器函数。为了解决这个问题,Python 提供了 functools.wraps 函数来保留主函数的元数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from functools import wraps


# Decorator
def add(f):
@wraps(f)
def wrap(*args, **kwargs):
"""Wrapper function for @add."""
x = kwargs.pop("x")
x += 1
x = f(*args, **kwargs, x=x)
x += 1
return x
return wrap


@add
def operations(x):
"""Basic operations."""
x += 1
return x


operations.__name__, operations.__doc__

# Output
# ('operations', 'Basic operations.')

太棒了!我们成功地使用装饰器来实现自定义,而不必修改函数本身。我们可以将这个装饰器用于其他需要相同自定义的函数中!

Callbacks

装饰器允许在主函数执行前后进行自定义操作,但是中间呢?假设我们想有条件地/有场景地执行一些操作。我们可以使用回调函数,而不是编写大量的if语句并使我们的函数臃肿。

回调:在函数内的有条件/有场景地执行任务。

我们定义一个有特殊函数的类,这些函数将在主函数执行期间的各个时期执行。 函数名称由我们决定,但我们需要在主要功能中调用相同的回调函数。

1
2
3
4
5
6
7
8
# Callback
class XTracker(object):
def __init__(self, x):
self.history = []
def at_start(self, x):
self.history.append(x)
def at_end(self, x):
self.history.append(x)

我们可以传入任意数量的回调函数,因为它们具有特定命名的函数,所以它们将在适当的时间被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def operations(x, callbacks=[]):
"""Basic operations."""
for callback in callbacks:
callback.at_start(x)
x += 1
for callback in callbacks:
callback.at_end(x)
return x


x = 1
tracker = XTracker(x=x)
operations(x=x, callbacks=[tracker])

# Output
# 2


tracker.history

# Output
# [1, 2]

Putting it all together

decorators + callbacks = 不增加复杂度的情况下在函数执行的前、中或后执行超强的自定义任务。 我们将在未来的中使用这个组合来创建强大的机器学习/深度学习训练脚本,这些脚本具有高度可定制性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from functools import wraps


# Decorator
def add(f):
@wraps(f)
def wrap(*args, **kwargs):
"""Wrapper function for @add."""
x = kwargs.pop("x") # .get() if not altering x
x += 1 # executes before function f
x = f(*args, **kwargs, x=x)
# can do things post function f as well
return x
return wrap


# Callback
class XTracker(object):
def __init__(self, x):
self.history = [x]
def at_start(self, x):
self.history.append(x)
def at_end(self, x):
self.history.append(x)


# Main function
@add
def operations(x, callbacks=[]):
"""Basic operations."""
for callback in callbacks:
callback.at_start(x)
x += 1
for callback in callbacks:
callback.at_end(x)
return x


x = 1
tracker = XTracker(x=x)
operations(x=x, callbacks=[tracker])

# Output
# 3


tracker.history

# Output
# [1, 2, 3]

Citation

1
2
3
4
5
6
7
@article{madewithml,
author = {Goku Mohandas},
title = { Python - Made With ML },
howpublished = {\url{https://madewithml.com/}},
year = {2022}
}

Ending

到这里,便拥有了Way2AI路上需要的Python的必备知识。

但我们不应该止步于此,应该学习更多的Python知识以面对更大的挑战。
Python官网 上有关于Python这门语言全部知识。