快速上手 Python

    Programming Language

JavaScript 这类语言的设计,适合新手学习理解特别简单编程概念。比如函数。但如果要理解面向对象(Object Oriented)的概念,用 JavaScript 会比较绕。因为它设计比较奇怪,你得去理解一些专门针对 JavaScript 设计的东西。

相比之下,用 Python 来理解面向对象,思路会清晰很多。


Python 编程环境

一般推荐两个编辑器,或者说 IDE ,来快速实践各种代码:

此外,还可以选择 安装配置 Anaconda ,以使用 Anaconda 附带的 Python 在终端里运行。

## 通过 Homebrew 安装 python
$ brew install python3

## 查看 Python 的版本
$ python3 --version
Python 3.6.5 :: Anaconda custom (64-bit)

## 运行 *.py 的 Python 脚本文件
$ cat ~/scripts/first_script.py         # 显示 first_script.py 文件里的内容
print('Congratulations on running this script!!')

$ python3 ~/scripts/first_script.py     # 用 python3 运行 first_script.py 文件
Congratulations on running this script!!

## 直接输入命令 python ,不带任何文件和参数,会进入 interactive interpreter(交互式解释器),可进行各种 Python 的语法测试
$ python3
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>exit()
$

PS:
个人推荐新手使用 VS Code ,这东西介于编辑器和 IDE(Integrated Development Environment)之间,既有编辑器的简洁,又有 IDE 的强大功能,关键是安装配置起来特别简单,最适合新手。
可以参考这篇上手 Visual Studio Code 设置 Python 的运行环境。


Python 的一些基本规则

  1. 变量区分大小写,即 case sensitive
  2. 空格重要,附带语意,即 spacing matters
  3. 运算符号有优先级(比如乘除法 * / 优先级高于加减法 +-)
  4. 运算的优先级可以使用括号来确定:优先级方向为从里往外
  5. Python 中名为 _ 的特殊变量(underscore variable)参见这里
  6. Python 变量名的规则不用 listSum 这种大小写混合,而是用下划线分隔多个单词,写成 list_sum 这样
  7. Python 赋值时是「传址(引用)」效果,而不是「传值」效果。见下例:

    # 感受一下「传址」效果
    def li_round_change_value(li):
     for i,l in enumerate(li):
       li[i] = round(float(l), 3)
    
    def li_round_doesnt_change_value(li):
     # 使用 map(function_to_apply, list_of_inputs) 将 list 里元素的类型变为浮点(float)型
     li = list(map(lambda k:round(float(k), 3), li))
     return li
    
    test_li = [9,13,5,2]
    print(li_round_doesnt_change_value(test_li)) # 输出 [9.0, 13.0, 5.0, 2.0] 
    print(test_li) # 输出 [9, 13, 5, 2]
    
    li_round_change_value(test_li)
    print(test_li) # 输出 [9.0, 13.0, 5.0, 2.0]
    

附:
Python 命名规范


Python 中的注释

  • # 开头的行
  • """(三个双引号)包裹的代码块(Code Block)
## codes by chpwang - 2018-05-17 04:57:56

"""
任务3:
xxxxxx
"""

变量定义

Python 里面定义变量不需要 var 这类关键词,直接写就行,末尾也不用分号。

## 变量定义
x = 2 * 3

## 赋值
x = 12

## 变量定义还可以解析结构
l1 = [9, 8]
[u, v] = l1

print(u)  # 9
print(v)  # 8

函数

Python 里面经常用「冒号」来分隔语言的各个部分。这里参数后面是冒号。新手要注意,冒号容易忘记写。

## 匿名函数 x => x * x
lambda x: x * x

## 函数调用
(lambda x: x * x)(3)

## 函数定义
square1 = lambda x: x*x

def square2(x): return x*x

## 这里「缩进」很重要,python 靠缩进来判断函数体
## 如果函数体语句多,注意缩进要对齐,不能有的缩进 2 格,有的只缩进 1 格
## 「缩进」是一种危险的糟糕设计,容易出错,比如有时 return 缩进错了,可能就跑到外部去了
## 缩进设计似乎只是为了少写一些括号
def square3(x):
  return x*x


result1 = 10
result2 = 10

## square4 中的 result 是局部变量,不改变外面的 result
def square4(x):
  result1 = x*x
  return result

## 加了 global 关键词后,square5 中的 result 就是全局变量了
## 调用 square5 会改变外面的 result
def square5(x):
  global result
  result = x*x
  return result

print(square4(4))  # 16
print(result1)     # 10
print(square5(4))  # 16
print(result1)     # 16


## 函数也可以什么都不做,但要写一个 pass ,不能什么都不写
def do_nothing(x):
  pass
>>> def minus(x, y): return x - y
...
>>> minus(9, 6)
3
>>> minus(y=9, x=6)  # python 的函数调用可以指定参数的值,这样可以无视参数的调用顺序
-3                   # 好处是如果参数比较多,把各参数名和值写上,代码阅读起来就很直观 - 这是个好设计

基本数据类型

  • 整数 - int

    >>> 2 * 3
    6
    >>> 3 // 2  # 取整
    1
    
  • 浮点数 - float
    整数和浮点数在电脑里是有差别的,整数和浮点数混合计算会被转化为浮点数,结果也是浮点数

    >>> 3 / 2
    1.5
    >>> 1.5 * 2
    3.0
    
  • 复数 - complex
    这是一种用于数学计算的数据类型,Python 中用 j 来表示虚数

    >>> (1 + 2j) * (1 + 2j)
    (-3+4j)
    
  • 字符串 - string
    可以用单引号 'hello' ,也可以用双引号 "hello"
    一个常用的字符串方法:str.split()

    >>> '1,2,3'.split(',')
    ['1', '2', '3']
    >>> '1,2,3'.split(',', maxsplit=1)
    ['1', '2,3']
    >>> "1,2,,3,".split(',')
    ['1', '2', '', '3', '']
    
  • 布尔类型 - bool
    Python 中不用 && 而是用 and 来代表「
    其实 && 并不是一个好设计,因为这个符号没有 and 直观易懂

    >>> 1<2 and 3>4
    False
    >>> 1<2 or 3>4
    True
    >>> not 1<2
    False
    >>> 1 == 2
    False
    >>> type(1==2)
    <class 'bool'>                # 注意这里返回的类型,不是「字符串」
    >>> type(type(1==2))
    <class 'type'>                # 它是一个 type 类,即 class 'type'
    >>> type(1==2) == "bool"      # 所以不能用这种方式来判断数据的类型
    False
    >>> type(1==2) == type(1<2)   # 对应地,可以用这种方式来判断数据类型
    True
    >>> type(1==2) == type(False) # Python 的类型设计是要比 JavaScript 严格很多的
    True                          # 所以做科学计算或者 AI 用的就是 Python 而不是 JavaScript
    
    ## 正规判断类型的方法是用 isinstance(object, type)
    >>> isinstance(2, bool)
    False
    >>> isinstance(2 < 3, bool)
    True
    
  • None
    这是 Python 里面的一种专门的数据类型,代表「没有」,类似 JavaScript 里的 Null

    >>> None         # 注意第一个字母 N 是大写
    >>> x = None
    >>> x            # 没输出不显示
    >>> print(x)     # 用 print 能打印出来
    None
    >>> print(None)
    None
    

基本的数据结构

  • 列表 - List
    在编程中,「链表(Linked List)」和「列表(List)」通常不是同一个概念:
    链表是一种数据结构,由一系列节点组成,每个节点包含数据以及指向下一个节点的指针;
    列表是一种抽象数据类型(ADT),用来存储一系列元素的有序集合。列表可以有不同的实现方式,包括数组、链表等,甚至可以是其他数据结构的组合。链表列表的一种实现方式;
    在编程中,「列表」通常更广泛地指代一种抽象数据类型,而「链表」则更具体地指代一种特定的数据结构。
    因此,它们的主要区别在于抽象性和具体性;

    >>> li = ['1', '2', '3']
    
    ## li 的定义看起来像数组,但其实不是,它的类型是 class 'list'
    ## 但你可以认为它就是一个数组
    >>> type(li)
    <class 'list'>
    
    ## 所以你可以用下标访问 - 下标从 0 开始
    >>> li[0]
    1
    
    ## slice 操作
    ## 访问 list 中下标从 0 到 2 的部分 - 注意这里不包括 li[2]
    ## 相当于 [0, 2) 的感觉
    >>> li[0:2]
    [1, 2]
    
    ## 访问从头到某个下标(不包括 li[1])
    >>> li[:1]
    [1]
    
    ## 访问从某个下标到最后
    >>> li[1:]
    [2, 3]
    
    ## 赋值或者定义的时候,传递的是引用,而不是复制一份
    >>> li
    [1, 2, 3]
    >>> li2 = li
    >>> li2[1] = 9    # 可以看到 li2 改变的同时,li 也被改变了
    >>> li
    [1, 9, 3]
    
    ## 如果要复制一份而不是传递引用,需要用 .copy() 这个方法
    >>> li2 = li.copy()
    >>> li[1] = 2
    >>> li2
    [1, 9, 3]
    >>> li
    [1, 2, 3]     # 所以在使用 python 的时候你要注意它是同一个对象还是不同的
    
    ## 越界访问会报错 - 这是个好设计
    ## 如果返回 undefined 之类的数据,就有可能被到处传递引发潜在问题而难以被定位纠正
    >>> li[3]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    IndexError: list index out of range
    
    ## 计算列表长度
    >>> len(li)
    3
    
    ## 使用 dir 可以看到一个类型(对象)里有哪些方法(methods)
    >>> dir(li)
    ['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', 
    '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', 
    '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', 
    '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', 
    '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 
    'reverse', 'sort']
    
    ## 按 2 次 Tab 键进行补全,它就会提示你当前可用的所有方法(methods)
    >>> li.
    li.append(  li.copy(    li.extend(  li.insert(  li.remove(  li.sort(
    li.clear(   li.count(   li.index(   li.pop(     li.reverse(
    
    ## 插入元素 - 这里 insert 元素到开头还是比较费事的,不像 pair 那种 list 时间复杂度只有 O(1)
    ## 为了程序的效率,一般不会把东西插到中间
    >>> li.insert(0, 9)
    >>> li
    [9, 1, 2, 3]
    
    ## 排序
    >>> li.sort()
    >>> li
    [1, 2, 3, 9]
    
    ## 取出最后一个元素
    >>> li.pop()
    9
    >>> li
    [1, 2, 3]
    
    ## 列表里增加新元素(element)
    ## 效果一
    >>> li.append([4])
    >>> li
    ['1', '2', '3', [4]]  # 可以看到 list 里每个元素的类型不一定要相同 
    
    ## 效果二
    >>> li.extend([4])
    >>> li
    ['1', '2', '3', [4], 4]
    
    ## 效果三
    >>> li + [5]
    ['1', '2', '3', [4], 4, 5]
    >>> li
    ['1', '2', '3', [4], 4]
    
    ## 使用「列表推导式(List Comprehension)」来生成 List
    >>> li_c = [x**2 for x in range(10)]
    >>> li_c
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    # 「列表推导式」中的 if
    >>> [x for x in li_c if x % 2 == 0]
    [0, 4, 16, 36, 64]
    
    ## 「列表推导式」中的 if-else
    ## 注意这里用了跟上面单纯的 if 不同的结构,所以 if 和 for 的位置顺序也不一样  
    ## The expression 「 x if C else y 」 first evaluates the condition, C rather than x. 
    ## If C is true, x is evaluated and its value is returned.
    ## Otherwise, y is evaluated and its value is returned.
    >>> [x if x % 2 == 0 else 3 for x in li_c]
    [0, 3, 4, 3, 16, 3, 36, 3, 64, 3]
    
    ## 「列表推导式」中的「列表推导式」- 嵌套
    >>> [ [x**2 for x in li] for li in [[1,3,5], [2,4,6]] ]
    [[1, 9, 25], [4, 16, 36]]
    
    ## 对 List 中的元素进行 map 操作
    >>> list(map(lambda x:x+1, li_c))
    [1, 2, 5, 10, 17, 26, 37, 50, 65, 82]
    
  • 元组 - tuple
    tuplelist 的区别在于,tuple 是不可更改的

    ## Tuple 用的是括号来表示,区别于 list 的方括号 []
    >>> t = (1, 2, 3, 4, 5, 5, 5)
    >>> t[1]
    2
    
    ## 无法更改
    >>> t[1] = 0
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'tuple' object does not support item assignment
    
    ## len 函数可以接收多种数据类型 - 这里是一个面向对象语言好的地方
    >>> len(t)
    7
    
  • 字典 - dict
    字典本质上就是哈希表,即键值对(key-value pair)。比如对于 {'Ace': 1, 'Queen': 12} 这一字典来说,‘Ace’‘Queen’ 就是(Key),而对应的 112 就是(Value)

    ## dict 用花括号来表示,类型是 class 'dict'
    >>> d = {'a': 1, 'b': 2}
    >>> d
    {'a': 1, 'b': 2}
    >>> type(d)
    <class 'dict'>
    
    ## 你需要用下标来访问,而不能当成对象(object)来用
    ## Python 里把 dict 和 object 区分开了 - 这是一个好设计
    >>> d['a']
    1
    >>> d.a                                # 用 .a 来访问会报错
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'dict' object has no attribute 'a'
    
    ## 修改和添加
    >>> d['a'] = 5         # 赋值
    >>> d['c'] = 12        # 添加新的 key
    >>> d
    {'a': 5, 'b': 2, 'c': 12}
    
    ## 判断某个 key 是否存在与某个字典中(注:case sensitive)
    >>> "ace" in {'ace': 1}
    True
    
    ## 计算字典长度
    >>> state_value = {(6, 10, False): -1.0, (15, 10, False): -1.0}
    >>> len(state_value)
    2
    
    ## 使用「列表推导式(List Comprehension)」来生成字典(dictionary)
    >>> vocab_to_int = {word: ii for ii, word in int_to_vocab.items()}
    
    ## 这里也可以用「列表推导式(List Comprehension)」来实现使用函数 f 来对字典的内容进行 Map
    {k: f(v) for k, v in my_dictionary.items()}
    

    模组 collections 中的 defaultdict 类能更方便地处理 value 为列表(list)、字典(dictionary)的情况,其用法参见Using defaultdict in Python

    >>> from collections import defaultdict
    >>> city_list = [('TX','Austin'), ('TX','Houston'), ('NY','Albany'), ('NY', 'Syracuse'), ('NY', 'Buffalo'), ('NY', 'Rochester'), ('TX', 'Dallas'), ('CA','Sacramento'), ('CA', 'Palo Alto'), ('GA', 'Atlanta')]
    >>>
    >>> cities_by_state = defaultdict(list)
    >>> for state, city in city_list:
    ...     cities_by_state[state].append(city)
    ...
    for state, cities in cities_by_state.iteritems():
    ...     print state, ', '.join(cities)
    ...
    NY Albany, Syracuse, Buffalo, Rochester
    CA Sacramento, Palo Alto
    GA Atlanta
    TX Austin, Houston, Dallas
    
  • 集合 - set
    集合也是数学计算中会常用到的数据结构:

    ## Set 也是花括号,可以注意到内部的顺序是乱的
    >>> s = {'a', 'b', 'c', 't', 'hello'}
    >>> s
    {'t', 'b', 'hello', 'c', 'a'}
    
    ## 增加元素
    >>> s.add('world')
    >>> s
    {'world', 't', 'b', 'hello', 'c', 'a'}
    
    ## 判断某个元素是否存在 - 可以认为这是个二元操作(binop)
    >>> 't' in s
    True
    >>> 'hi' in s
    False
    
    ## set 的相关运算
    >>> s2 = {'a', 't', 'world', 'y', 'i'}
    >>> s - s2
    {'b', 'hello', 'c'}
    >>> s
    {'world', 't', 'b', 'hello', 'c', 'a'}             # 不会改变原来的集合 s
    >>> s.intersection(s2)
    {'world', 't', 'a'}                                # 交集
    >>> s.union(s2)
    {'world', 't', 'b', 'c', 'i', 'y', 'hello', 'a'}   # 并集
    

条件分支 - if 语句

注意else if 分支在这里写作 elif

def fib(n):
  if n == 0:
    return 0
  elif n == 1:
    return 1
  else:
    return fib(n-1) + fib(n-2)


if season == 'spring':
    print('plant the garden!')
elif season == 'summer':
    print('water the garden!')
else:
    print('unrecognized season')


## lambda 中的 if - 无法在 lambda 匿名函数中使用 print 或者 raise
lambda x: True if x % 2 == 0 else False

循环

## Python 中的循环一般不用下标
>>> a = [2, 3, 4, 6]
>>> for x in a:
...  print(x)
...
2
3
4
6

## 可以使用这种模式匹配来直接取出 key 和 value 而不用后续访问下标 - 这是好设计
>>> for [k, v] in [["dog", 1], ["cat", 2], ["fish", 3]]:
...     print(f"{k}: {v}")
...
dog: 1
cat: 2
fish: 3

## 模式匹配(pattern match)还可以用于赋值,比如交换变量的数据
>>> [x, y] = [3, 4]
>>> x
3
>>> y
4
>>> [x, y] = [y, x]
>>> x
4
>>> y
3

## 和 range(start, stop, step) 函数结合使用
>>> for i in range(0, 2, 1):
...     print(i)
...
0
1

## 这里说明一下这个 range
## 可以认为 range(5) 的效果就相当于生成了一个 [0, 1, 2, 3, 4] 这样的 list
## 然后我们就可以 for i in range(5)
## 虽然效果上是一样的,但实际上 range 不会真的生成一个 list
## range 多用于循环,如果真生成一个 list ,就会影响计算性能并占用空间
## 比如这样一来 range(1000000) 就很吃计算资源了
## range 实际上生成的是一种 lazy list(惰性列表)
## 惰性列表的意思是,它不会真的给你放到内存里面去
## range(5) 会返回一个 range 对象,按需计算元素的值,内存高效(memory efficient)

## python 中的 for 循环都要用 for x in ... 这种形式,如果 x 是是数字,就要用这个 range
## 有人认为这样的 for 循环表达能力有限,不能表达复杂的逻辑,range 只能表达简单的数字
## 比如无法写出类似 for(var i=0, j=array.length; i < j; i++, j--) 的代码
## 但其实这是 python 的一个好设计。当表达不了的时候,就不应该使用 for 循环,而要改用 while 循环
## 上述复杂的 for 循环需要思考到底是先判断 i < j 还是先计算 i++ 和 j--,不利于代码阅读理解

## 用 reversed 配合 range 倒数
## 写成 for i in range(4, -1, -1) 虽然是同样的效果,但阅读理解上不直观
>>> for i in reversed(range(0, 5)):
...     print(i)
...
4
3
2
1
0

# 和 zip() 函数结合使用
>>> alist = ['a1', 'a2', 'a3']
>>> blist = ['b1', 'b2', 'b3']
>>> for a, b in zip(alist, blist):
...     print(a, b)
...
a1 b1
a2 b2
a3 b3

## upack 一个 list 也是常用的循环 - 若觉得下行的说明不清楚就开终端试一试
## 此例第 1 次循环取出第一个元素 [1, 3],然后把第一项和第二项分别赋值给 a 和 b
>>> for a, b in [[1,3],[2,4]]:
...     print(a)
...     print(b)
...
1
3
2
4

## 使用 * (星号 star)和 zip 来操作 list
## 下划线符号:_ 在 Python 中代表占位符,相当于吃掉这两个位置的输出 - 下面的代码原本会输出 3 个结果,但只想要其中最后一个结果,所以使用下划线来抛弃前两个结果 
>>> _, _, rewards = zip(*[((6, 10, False), 1, 0), ((17, 10, True), 1, 0), ((21, 10, True), 0, 1.0)])
>>> rewards
(0, 0, 1.0)

## while 循环
>>> while i < 6:
...   print(i)
...   if i==3:
...     break
...   i += 1
...
1
2
3

类 - class

这里以实现查找表为例演示 class 的用法:

  • 类里面的函数的第一个参数都是 self ,也就是自己
  • 类里面默认要有 __init__ 函数作为「构造函数」,如果没写 Python 也会给一个默认的
## Map 没有定义 __init__ ,但还是程序还是会给它一个默认的 __init__
class Map:
  def print_data(self):
    print("data: " + str(self.data))


class Table:
  def __init__(self):
    self.data = []

  def add(self, key, value):
    self.data.insert(0, [key, value])

  def lookup(self, key):
    for [k, v] in self.data:
      if k == key:
        return v
    return None


class Hash:
  def __init__(self):
    self.data = {}

  def add(self, key, value):
    self.data[key] = value

  def lookup(self, key):
    return self.data[key]

输出

这里介绍的是常用的输出语法,更详细的介绍可以参考这篇 How to use str.format()

## 普通输出 - 使用 {} 占位,.format() 填充占位符
>>> print("{} ~ {}% dog wins.".format(3, 9))
3 ~ 9% dog wins.

## 保留 5 位小数
>>> print("{} - {:.5f}".format(3,9.0/3.5))
3 - 2.57143

## 保留 1 位小数
>>> print("{} - {:.1f}".format(3,9.0/3.5))
3 - 2.6

在 Python 中,还可以使用 f-string 这种种字符串格式化的方法来输出。只需在字符串前加上字母 fF ,然后在字符串中使用花括号 {} 包裹要插入的变量或表达式:

name = "Alice"
age = 30
height = 1.75

print(f"My name is {name}, I am {age} years old.")    # 使用 f-string 插入变量值
# My name is Alice, I am 30 years old.

print(f"Next year, I will be {age + 1} years old.")   # 可以在 f-string 中进行简单的表达式计算
# Next year, I will be 31 years old.

print(f"My height is {height:.2f} meters.")           # 使用 f-string 格式化浮点数
# My height is 1.75 meters.

自解释器

Python 中的内置函数 eval 可以将字符串解析成 Python 的代码

## test.py
num = 30
x = eval("num + 29")
print(x)
$ python test.py
59

创建与导入脚本

处理大型项目时,可以将代码分割,整理成多个 *.py 文件以便重复利用这些文件中的代码。这些 *.py 文件称为脚本(script)。如果你要导入的 Python 脚本与当前脚本位于同一个目录下,只需输入 import,然后是文件名,无需扩展名 .py

## demo.py
import useful_functions as uf

scores = [88, 92, 79, 93, 85]
mean = uf.mean(scores)

print("Demo Mean:", mean)
print("__name__ value:", __name__)
print("uf.__name__ value:", uf.__name__)
## useful_functions.py

def mean(num_list):
    return sum(num_list) / len(num_list)

def main():
    n_list = [34, 44, 23, 46, 12, 24]
    print("Useful Functions' Mean:", mean(n_list))

# 检测当前脚本(即 useful_functions.py)是否做为主模块被运行
# 当前脚本若是被其他脚本的 import 语句导入,则下面的 if main 块代码不运行
if __name__ == '__main__':
    main()

运行结果如下:

$ ls
demo.py             useful_functions.py
$ python demo.py
Demo Mean: 87.4
__name__ value: __main__
uf.__name__ value: useful_functions

人们通常只希望重复使用被导入脚本里的「函数」或「类」,而不是其他可执行代码(如 print()),所以为避免「脚本A」被导入后,其中的可执行语句被运行,应该要将这些语句包含在 if __name__ == "__main__"块中,或者,将它们包含在函数 main() 中并在上述 if main 块中调用该函数。

每当运行脚本时(*.py 文件),Python 会为所有模块(module)设置一个特殊的内置变量 __name__。这些模块包含导入模块(用 import 语句导入的),还有主模块(即当前脚本)。主模块的 __name__ 变量值会被设为字符串 "__main__",而对于被导入的模块,__name__ 变量会设为该模块的名称。因此 if main 代码块常用来判断是否为主模块。

PS:
从输出结果可看到,上述例子中,被导入模块的 __name__ 值为 "useful_functions"


自定义一个类 - 复杂例子

import math
from decimal import Decimal, getcontext

## 设置 Decimal 数据类型的小数点后保留的位数
getcontext().prec = 19
## 用来辅助判断一个数是否为零(小于此值则为零)
TOLERANCE = 1e-10

class Vector(object):

  ## 设置报错信息
  ALL_ELEMENT_MUST_BE_NUM_MSG = "Element must be number!"

  ## 实例(instance)的创建
  def __init__(self, coordinates):
    try:
      if not coordinates:
          raise ValueError
      self.coordinates = tuple([Decimal(x) for x in coordinates])
      self.dimension = len(coordinates)

    except ValueError:
      raise ValueError('The coordinates must be nonempty')

    except TypeError:
      raise TypeError('The coordinates must be an iterable')

  ## print 输出内容
  def __str__(self):
    return 'Vector: {}'.format(self.coordinates)

  ## 定义相等
  def __eq__(self, v):
    return self.coordinates == v.coordinates

  def __len__(self):
    return len(self.planes)

  ## __getitem__ 这个函数使得对于 v = Vector([5, 3, -2]) ,可使用索引方式 v[1] 得到数字 3
  def __getitem__(self, i):
    return self.coordinates[i]

  ## __setitem__ 这个函数使得对于 v = Vector([5, 3, -2]) ,可使用索引方式 v[1] = 9 将 v[1] 的值设置为 9
  def __setitem__(self, i, x):
    try:
      assert x.dimension == self.dimension  # 如果设置的 x 不是数字,则报错
      self.coordinates[i] = x

    except AssertionError:
      raise Exception(self.ALL_ELEMENT_MUST_BE_NUM_MSG)

  ## 计算向量的加法
  def plus(self, v):
    new_coordinates = [x+y for x,y in zip(self.coordinates, v.coordinates)]
    return Vector(new_coordinates)

  ## 计算向量的减法
  def minus(self, v):
    return self.plus(v.times_scalar(-1))

  ## 计算向量的数乘
  def times_scalar(self, c):
    new_coordinates = [x*c for x in self.coordinates]
    return Vector(new_coordinates)

  ## 计算向量与另一向量的点积(内积)
  def dot_product_with(self, v):
    new_coordinates = [x*y for x,y in zip(self.coordinates, v.coordinates)]
    return sum(new_coordinates)

  ## 计算向量的模
  def magnitude(self):
    return Decimal(math.sqrt(self.dot_product_with(self)))

  ## 计算向量标准化后的向量(同方向上的单位向量)
  def normalization(self):
    try:
      mag = self.magnitude()
      return self.times_scalar(Decimal(1)/mag)
    except ZeroDivisionError:
      #print("You can't normalize Zero Vector!")
      raise Exception("You can't normalize Zero Vector!")

  ## 计算向量与另一向量的夹角
  def angle_with(self, v, in_degrees=False):
    try:

      mag_1 = self.magnitude()
      mag_2 = v.magnitude()
      if in_degrees:
        return math.degrees(math.acos(self.dot_product_with(v)/(mag_1*mag_2)))
      else:
        return math.acos(self.dot_product_with(v)/(mag_1*mag_2))

    except ZeroDivisionError:
      #print("At least one of the vector is Zero Vector! No angle defined.")
      raise Exception("One of the vector is Zero Vector! No angle defined.")

  ## 私有函数(private function)和变量,以两个下划线开头来命名
  def __is_zero_vector(self):
    return self.magnitude() < TOLERANCE

  def is_parallel_to(self, v):
    if v.__is_zero_vector() or self.__is_zero_vector():
      return True
    else:
      s_m = self.normalization()
      v_m = v.normalization()
      return s_m.minus(v_m).__is_zero_vector() or s_m.plus(v_m).__is_zero_vector() 

  def is_orthogonal_to(self, v):
    return abs(self.dot_product_with(v)) < TOLERANCE

  ## 向量的分解 - 计算向量水平方向的分量
  def component_parallel_to(self, base_vactor):
    u_b = base_vactor.normalization()
    mag = self.dot_product_with(u_b)
    return u_b.times_scalar(mag)

  ## 向量的分解 - 计算向量竖直方向的分量
  def component_orthogonal_to(self, base_vactor):
    c_p = self.component_parallel_to(base_vactor)
    return self.minus(c_p)

  ## 计算向量与另一向量的叉乘
  def cross_product_with(self, v):
    if self.dimension != 3 or v.dimension != 3:
      raise Exception("Both cross product vectors must be three dimensional")

    if self.is_parallel_to(v):
      return Vector([0 for i in range(self.dimension)])
    else:
      x = self.coordinates[1]*v.coordinates[2] - self.coordinates[2]*v.coordinates[1]
      y = self.coordinates[2]*v.coordinates[0] - self.coordinates[0]*v.coordinates[2]
      z = self.coordinates[0]*v.coordinates[1] - self.coordinates[1]*v.coordinates[0]
      return Vector([x, y, z])

  ## 计算向量与另一向量的所围成的四边形的面积
  def area_of_parallelogram_spanned_with(self, v):
    new_self = self
    new_v = v
    if self.dimension == 2:
      new_self = Vector(self.coordinates + (0,))
    if v.dimension == 2:
      new_v = Vector(v.coordinates + (0,))

    return new_self.cross_product_with(new_v).magnitude()

  ## 计算向量与另一向量的所围成的三角形的面积
  def area_of_triangle_spanned_with(self, v):
    return self.area_of_parallelogram_spanned_with(v) / Decimal(2)

常用模组 - Module

Python 的标准库里包含了常用的 Module ,使用 import 语句可导入

以下是一些实用的模组(Module):

  • sys:内建了很多好用的工具

    import sys
    
    ## 工具一:sys.stdout.flush() 
    ## 不断更新输出内容,而不是叠加输出(即每次删除上次的「显示结果」,然后显示当次的「显示结果」)
    for i_episode in range(1, num_episodes+1):
            # monitor progress
            if i_episode % 1000 == 0:
                print("\rEpisode {}/{}.".format(i_episode, num_episodes), end="")
                sys.stdout.flush()
    
  • re :通过正则表达式在字符串中进行模式匹配

  • math :一些标准数学函数

  • fractions:此库最常用的是里面的 Fraction,分数(有理数)计算超方便

    from fractions import Fraction
    
  • random :生成假随机数字,随机打乱序列并选择随机项

  • json :适用于读写 json 文件(面向网络开发)

  • datime :读取当前时间和日期

  • os :与操作系统交互的操作

    import os
    os.chdir(path)  # 更改当前的工作目录(working directory)
    
  • csv :处理 .csv 类型的文件

  • zipfile :处理 .zip 压缩文件

  • collections :常见数据类型的实用扩展,包括 OrderedDict、defaultdict 和 namedtuple

  • timeit :显示简短的代码运行所花费的时间

  • cProfile 或 profile :显示大型项目运行所花费的时间


打赏