Endsieg77's Studio.

Highway to Python

2021/01/12 Share

Overture

Brief Introduction

  Python是一种动态的(变量类型, 函数定义可以在代码中改变)、强类型的(不同类型之间的运算有严格的限制)脚本语言(也叫胶水语言)(用来拼接一个项目中不同语言的成分)。

  其由荷兰数学和计算机科学研究学会的Guido van Rossum 于1990 年代初设计。名字来源为英国20世纪70年代首播的电视喜剧《蒙提·派森的飞行马戏团》(Monty Python’s Flying Circus)。Python 2和Python 3有很大区别,现在Python 2已经停止更新。我们一般情况下介绍的都是Python 3。

  Python涵盖了许多有用的库,可以用于数学计算、数据可视化、机器学习、信息安全等各个领域。

  Python如今在广大非计算机专业也是一门常见的入门语言。

  在Python中, Indent(缩进)是一个非常重要的概念。Python程序员应该养成良好的缩进习惯。


Python Zen

在Python Shell里输入import this

终端会给出如下的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

算是个小彩蛋。Coding是浪漫的,不是吗?


Insert User Snippets in vscode

文件->首选项->用户片段

向对话框中输入python,

在跳出的python.json文件里加入下列片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"Print to console": {
"prefix": "main", //用户片段名
"body": [
"def main():",
" $0", //光标起始位置
"", //空行
"if __name__ == \"__main__\":",
" main()",
""
],
"description": "Log output to console"
}
}

保存。

然后在后缀为py的文件中键入main,按tab(或手动/enter也行),便会自动跳出:

1
2
3
4
5
def main():


if __name__ == "__main__":
main()

你可以往用户片段中加入各种实用的片段,大大提高编程的效率。

其他语言的配置方法都是类似的。这里不多赘述。


Python Syntaxes & Syntatic Sugar

Identifier & condition statement

在Python中,变量名可以是中文。

a <= x <= b 这样的条件语句,在Python中可以得到C中a <= x && b >= x的效果

Python中的逻辑运算相关的关键字: and, or, true, false

注意: Python中不存在自带的switch语句.


Python的三目运算符

x if condition else y: 若condition成立,返回x,否则y。

实例详见string process - 一道简单的OJ题


string process


字符串切片

s[index]: 首字符从0开始计,返回第index位的字符。若index为负,则以最后一个字符为-1,以此类推。

s[pos_1 = 0: pos_2 = -1]: 从pos_1开始,到pos_2前一位结束,0和-1为缺省值。s[:]代表整个字符串。

s[pos_1 = 0: pos_2 = -1: pace = 1]: pace为步长。每隔pace截取一个字符。若pace为负值,则得到的结果是将s逆向,以终止位置开始,起始位置结束,取pace的绝对值所得到的结果。s[::-1]得到逆向的字符串。


find()

查找子串在原串中的位置,若没有,返回-1。


strip()

去除字符串两边的空格。


lower(), upper()

字面意思,大小写转换。


split(), join()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 一行高精度
# split()将输入按某字符分割,缺省为空格,读到\n结束,返回一个列表
# map(_Pred, _Iterable)将_Pred函数作用于_Iterable,结果是一个map对象
# list()将map转为list
# sum()取得list的数值和
print(sum(list(map(int, input().split()))))

# _Pred部分也可以填入lambda表达式
# 例如:
print(sum(list(map(lambda x: int(x), input().split()))))

# 与上面等效
# 和map()形式类似的函数还有reduce()和filter()
# reduce()将两参函数作用于列表的前两个元素,再将前两个元素的结果作用于第三个函数,如是往复
# filter()则是筛子

# join()是split()的逆方法。
seq = ["1", "2", "3"]
print('4'.join(seq))
# 输出: 14243

# lambda
lambda [params]: expr
# 比其他语言的lambda要简洁许多

三引号

允许定义多行字符串。如:

1
2
3
4
5
s = '''
CSS
is
Awesome.
'''

打印s,将会保留原格式。


RegExp

1
2
3
4
5
6
7
8
9
10
11
12
13
# 导入re模块
import re
# match()函数
re.match(pattern, string, flags=0)
# pattern为正则式,string为要匹配的字符串,flags为匹配模式
# 匹配模式:
# re.I 匹配对大小写不敏感
# re.L 本地化识别
# re.M 多行匹配,影响^和$
# re.S 使`.`匹配换行之外所有字符
# re.U 根据unicode集解析字符,影响\w, \W, \b, \B
# re.X 可以用更灵活的格式让你的正则式更容易理解
# 详见:https://www.runoob.com/python/python-reg-expressions.html#flags

group(), groups() 方法

我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re
a = "123abc456"
pattern = "([0-9]*)([a-z]*)([0-9]*)"

# 直观地看,每个括号代表一个分组,group()缺省为0

# 123abc456,返回整体
print(re.search(pattern,a).group(0))
# 123
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1))
#abc
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2))
#456
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3))
# ('123', 'abc', '456')
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).groups())

另一个实例:

1
2
3
4
5
6
7
8
9
10
11
12
import re

line = "Cats are smarter than dogs"

matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I)

if matchObj:
print("matchObj.group() : ", matchObj.group())
print("matchObj.group(1) : ", matchObj.group(1))
print("matchObj.group(2) : ", matchObj.group(2))
else:
print("No match!!")

结果:

matchObj.group() : Cats are smarter than dogs
matchObj.group(1) : Cats
matchObj.group(2) : smarter

start(), end(), span() 方法

start() 返回匹配字符串的起始位置;

end() 返回终止位置;

span() 返回一个元组,分别为起始和终止位置。

一道简单OJ题实战

输入格式:”三个字母一个数字”,字母可能是”USD”或”RMB”。输出USD和RMB互换的结果。

输出格式”三个字母一个数字”,数字保留到小数点后两位。

USD和RMB汇率6.78。

两种写法: (本题中所涉及的格式化输出请见本文以下的格式化输出章节。)

1
2
3
4
5
6
7
8
# 用正则表达式,仅仅为了熟悉Python的正则
import re
a = re.search(r"([a-z]+)([\d]+)", input(), flags=re.I).groups()
print(f"RMB{float(a[1])*6.78:.2f}" if a[0] == 'USD' else f"USD{float(a[1])/6.78:.2f}")

# 两行搞定
s = input()
print(f"RMB{float(s[3:])*6.78:.2f}" if s[:3] == 'USD' else f"USD{float(s[3:])/6.78:.2f}")

x for x in... if...语句

1
2
3
4
5
# 这种写法非常简洁美观
# 筛选list中3的整倍数
foo = [18, 9, 17, 5, 8]
print([x for x in foo if x%3==0])
# 类似C++的range-based for

输出:

[18, 9]


Overload in Python

  Python中不存在原生支持严格意义的函数重载,对函数的每一次重定义都将会覆盖上一个函数。参数类型不同的函数重载只能用逻辑判断来实现。


pass

  什么也不做。用作占位语句。等价于C语言的;


Python generator & iterable

  带有yield的函数在Python中为generator(生成器)。generator在python中生成一个可迭代(iterable)的对象。

  1. 对可迭代对象可以进行range-based for (类定义了__iter__()方法),和__getitem__()(不必要)。
  2. 常见的可迭代对象:(1) list、tuple、dict、set、str; (2) generator;
  3. 判断对象是否可迭代:isinstance(e, collections.Iterable)

链接:

yield菜鸟教程

深入理解可迭代


class & OOP

Python的一个基本的类定义如下:

1
2
3
4
5
6
7
class className():
# attributes block

def __init__(params):
# init body, 如果不需要,可以填入pass

#Other Methods

类的attribute利用.运算符访问。类似C的结构体。

Snippet:

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
def main():
# class definition:
class P:
# public:
name = ""
# protected:
_age = 0
# private:
__gender = "Female"

# constructor with default params
def __init__(self, name, gender="Female", age=10):
self.name = name
self.__gender = gender
self._age = age
# method
def selfIntro(self):
print("My name is %s, age is %d and I am %s" % \
(self.name, self._age, self.__gender))

p1 = P("LG")
p2 = P("ZG", "Female", 5)
p3 = P(gender="Male", name="XQ", age=15)
p1.selfIntro()
p1.__gender = "Male"
p1.selfIntro()
p1._age = 20
p1.selfIntro()
p2.selfIntro()
p3.selfIntro()

if __name__ == "__main__":
main()

同时我们发现:

p1调用自我介绍函数的时候的三次输出分别为:

1
2
3
My name is LG, age is 10 and I am Female
My name is LG, age is 10 and I am Female
My name is LG, age is 20 and I am Female

可以看出,对private类型成员变量修改后不会改变变量的值,也不会有报错信息。

public类型成员变量修改后确确实实发生了改变。(原因未知)


格式化输出

在上文片段中,如果将:

1
2
3
# C风格格式化输出
print("My name is %s, age is %d and I am %s" % \
(self.name, self._age, self.__gender))

替换为:

1
print(f"My name is {self.name}, age is {self._age} and I am {self.__gender}")

效果是一样的。Python的格式化输出功能要比原生C, C++要完善不少。

Python中还可以对字符串类型调用format()方法来达到格式化输出的效果。


global

  Python中若要使函数体内对变量的改变作用于函数体外,可以在变量前加上global关键字。


zip()

1
2
3
4
5
6
def zip(iter1: Iterable[_T1])
zip(*iterables) --> A zip object yielding tuples until an input is exhausted.

list(zip('abcdefg', range(3), range(4)))
[('a', 0, 0), ('b', 1, 1), ('c', 2, 2)]
The zip object yields n-length tuples, where n is the number of iterables passed as positional arguments to zip(). The i-th element in every tuple comes from the i-th iterable argument to zip(). This continues until the shortest argument is exhausted.

eval()

将字符串作为一个Python表达式执行。和Php的eval()类似。

1
2
3
4
a = 1
b = 2
c = eval("a if a > b else b")
print(c)

输出:2

Container

tuple

不可变的序列。

注意:单值元组在定义时要在元素后加一个,,如tp = (12,),防止语义冲突。


tuple的连接

类似tp3 = tp1 + tp2

tuple的删除

del tp

more details see: python中del的用法

elem in tp

判断某元素是否在tuple中,返回布尔值。

切片

参见string process一节。规则是类似的。

tuple的相关函数

min(tp), max(tp), len(tp), tuple(seq)

字面意思,很好理解。

tuple是最基本的序列,对tuple可以做的所有操作几乎都可以用在其他序列中。故下文将尽量避免冗余。


list

可以增删查改的序列。用[]定义。


list可以用+连接,同样,可以用del删除某一元素。也可以运用infind()等。

方法 作用
append(obj) 向列表最后添加一个元素。
count(obj) 返回obj在列表中出现的次数。
extend(seq) 将seq追加到原列表中。
index(obj) 获取第一个出现的obj的索引。如要获取最后一个,len(list) - list[::-1].index(obj) - 1是一种方法。
insert(index, obj) 将obj插入列表索引index的位置。
pop(index = -1) 删除列表一个元素。默认索引-1。即删除最后一个。并返回该元素的值。
remove(obj) 移除列表中第一个obj。
reverse() 列表反向。
sort(key = None, reverse = False) 排序。
key为函数,指定用元素的哪一项进行比较。可以为lambda函数。
reverse = False代表升序。
sort的原型:sort(*, key: Optional[Callable[[_T], Any]]=…, reverse: bool=…) -> None

set, dict

没什么好说的,跳过了。

more details see:

Python dict

Python set

Inside Python Mechanism

One

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
c = {}

def foo():
f = dict(zip(list("abcd"), [1, 2, 3, 4]))
c.update(f)

def main():
a = b = c
foo()
a['e'] = 5
b['f'] = 6
print(f"{a}\n{b}\n{c}")

if __name__ == "__main__":
main()

输出:

1
2
3
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}

深入理解Python变量赋值问题

Python与对象


mutable & immutable object

python中的变量间赋值本质上为传引用。而用字面值对变量赋值,根据对象的不同,会产生不同的行为。

我们依据这一性质将对象分为两类:

  1. 不可变。常见的不可变对象为int, float, str, complex, tuple;
  2. 可变。常见的可变对象为list和dict.

我们将从下面的代码理解Python的这一特性。

1
2
3
4
5
6
7
8
def main():
a = b = 10
print(f"addr of a is: {id(a)}, b is: {id(b)}")
b = 5
print(f"addr of a is: {id(a)}, b is: {id(b)}")

if __name__ == "__main__":
main()

两次的输出结果:

addr of a is: 2347657685584, b is: 2347657685584
addr of a is: 2347657685584, b is: 2347657685424

可见,Python对不可变对象的字面值赋值处理方式是:

开辟一块新的内存,让被改变的对象指向这块内存。其他的对象的内存指向不改变。


而对于可变对象,处理的方式便不同了。

对可变对象的修改不会开辟新的内存空间。但也有特例,如果赋予可变对象一个不可变对象字面值,那么可变对象的仍会开辟新的内存地址。


下面给出一个规律性的总结:

  • Python的所有赋值均为传引用。Python优雅的a, b = b, a就得益于Python的传引用方式。

用C++的方式来理解a, b = b, a的原理:

1
2
3
4
auto pb = &b;
auto pa = &a;
&a = pb;
&b = pa;

对于a, b, c = c, a, b的情形,其原理也是一样的。

但不同的是,C++不可以随意修改变量的地址,上述代码无法通过编译,所以Python的底层做法应该不是这样的。

  • 不可变对象被初始化的时候,编译器分配一个随机的地址给这个对象。值相同的对象指向同一个地址。当对象值改变,其地址也随之改变。
  • 可变对象被初始化的时候,编译器同样会给其分配一个随机地址。值相同的对象未必指向同一个地址。即使给同一对象赋前后相同的值,其地址也会改变。但如果仅对对象的某个属性,或者存储的元素进行修改,对象本身的地址不会发生改变。与C不同的是,Python中任何的容器都不会开辟一段连续的空间,尽管容器存放的元素地址可能改变,但容器本身地址并不会随之改变。

编写一个不可变的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 这里直接拷贝上述博文的代码
class It(object):
__slots__ = ['vals']

def __init__(self, vals):
super().__setattr__('vals',vals) # super()返回父类对象, 调用父类的
# __setattr__()方法,用'vals'来初始化自身。
def __setattr__(self, key, value):
raise AttributeError('不可变类') # 禁用本类的__setattr__()方法

def __eq__(self, other):
# return id(self) == id(other)
return self.vals == other.vals
def __hash__(self):
# return 1
# return random.randint(0,100)
return hash(';'.join(self.vals))
# return hash(id(self))

可变与可哈希

Python中,可变对象是不可哈希的,而不可变对象是可哈希的。


体现在字典上:{keys: vals}

键值对的键必须是不可变对象,因为不可变对象可以产生哈希值,而Python的字典底层是用哈希表实现的。(C++中的std::map底层则是红黑树,而std::unordered_map底层是哈希表)。

而对于值则没有要求。

不可变对象的哈希值通过调用__hash__()方法来获得,故用户自定义的不可变类必须给出合理的哈希函数重写。

CATALOG
  1. 1. Overture
    1. 1.1. Brief Introduction
    2. 1.2. Python Zen
    3. 1.3. Insert User Snippets in vscode
  2. 2. Python Syntaxes & Syntatic Sugar
    1. 2.1. Identifier & condition statement
      1. 2.1.1. Python的三目运算符
    2. 2.2. string process
      1. 2.2.1. 字符串切片
      2. 2.2.2. find()
      3. 2.2.3. strip()
      4. 2.2.4. lower(), upper()
      5. 2.2.5. split(), join()
      6. 2.2.6. 三引号
      7. 2.2.7. RegExp
        1. 2.2.7.1. group(), groups() 方法
        2. 2.2.7.2. start(), end(), span() 方法
      8. 2.2.8. 一道简单OJ题实战
    3. 2.3. x for x in... if...语句
    4. 2.4. Overload in Python
    5. 2.5. pass
    6. 2.6. Python generator & iterable
    7. 2.7. class & OOP
    8. 2.8. 格式化输出
    9. 2.9. global
    10. 2.10. zip()
    11. 2.11. eval()
  3. 3. Container
    1. 3.1. tuple
      1. 3.1.1. tuple的连接
      2. 3.1.2. tuple的删除
      3. 3.1.3. elem in tp
      4. 3.1.4. 切片
      5. 3.1.5. tuple的相关函数
    2. 3.2. list
    3. 3.3. set, dict
  4. 4. Inside Python Mechanism
    1. 4.1. One
      1. 4.1.1. mutable & immutable object
      2. 4.1.2. 编写一个不可变的类
      3. 4.1.3. 可变与可哈希