学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Python语法基础

Python生态健全,在量化数据分析,机器学习等领域使用广泛

数据结构

元组与列表

  • 已经有了列表这种数据结构,为什么还需要元组这样的类型呢?
  1. 元组中的元素是无法修改的,多线程环境中可能更喜欢使用的是那些不变对象,一个不变的对象要比可变的对象更加容易维护;一个不变对象自动就是线程安全的,如果一个方法要返回多个值,使用元组也是不错的选择

  2. 元组在创建时间和占用的空间上面都优于列表。

输入输出函数

input函数

  • 语法:
    input([prompt])

  • prompt: 提示信息

Python3.x 中 input() 函数接受一个标准输入数据,返回为 string 类型

注意:input() 和 raw_input() 这两个函数均能接收 字符串 ,但 raw_input() 直接读取控制台的输入(任何类型的输入它都可以接收)。而对于 input() ,它希望能够读取一个合法的 python 表达式,即你输入字符串的时候必须使用引号将它括起来,否则它会引发一个 SyntaxError 。
除非对 input() 有特别需要,否则一般情况下我们都是推荐使用 raw_input() 来与用户交互。

注意:python3 里 input() 默认接收到的是 str 类型。

print函数

  • 语法:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

  • 参数
  • objects – 复数,表示可以一次输出多个对象。输出多个对象时,需要用 , 分隔。
  • sep – 用来间隔多个对象,默认值是一个空格。
  • end – 用来设定以什么结尾。默认值是换行符 \n,我们可以换成其他字符串。
  • file – 要写入的文件对象。
  • flush – 输出是否被缓存通常决定于 file,但如果 flush 关键字参数为 True,流会被强制刷新。

返回值
无。

函数

Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python没有重载的概念

作用域

可以使用global关键字来指示foo函数中的变量a来自于全局作用域,如果全局作用域中没有a,那么下面一行的代码就会定义变量a并将其置于全局作用域。
同理,如果我们希望函数内部的函数能够修改嵌套作用域中的变量,可以使用nonlocal关键字来指示变量来自于嵌套作用域

可变参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple

1
2
3
4
5
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

关键字参数

关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict

1
2
3
4
5
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

>>> person('Michael', 30)
name: Michael age: 30 other: {}

模块

导入的模块除了定义函数之外还有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是"__main__"

面向对象

__init__是一个特殊方法用于在创建对象时进行初始化操作

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
 class Person:

amount = 0

def __init__(self,name,age,height):
self.name = name
self.age = 25
self.height = height
Person.amount +=1

def helloWorld(self):
print("Hello World")

def get_older(years):
self.age += years

def __str__(self):
return "Name:{},Age:{},Height:{}".format(self.name,self.age,self.height)

def __del__(self):
Person.amount -= 1
print("Object Deleted!")


person1 = Person("Jack",30,180)
print(person1.name)
print(person1.age)
print(person1.height)

person1.name = "Henry"
print(person1.name)
person1.helloWorld()


print(person1)



person2 = Person("Sara",40,160)
print(Person.amount)

del person1
print(Person.amount)

属性权限控制

Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Test:
def __init__(self, foo):
self.__foo = foo

def __bar(self):
print(self.__foo)
print('__bar')

def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)

if __name__ == "__main__":
main()

Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Test:

def __init__(self, foo):
self.__foo = foo

def __bar(self):
print(self.__foo)
print('__bar')


def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)


if __name__ == "__main__":
main()

@property装饰器

使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便

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
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age

# 访问器 - getter方法
@property
def name(self):
return self._name

# 访问器 - getter方法
@property
def age(self):
return self._age

# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age

def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)

def main():
person = Person('王大锤', 12)
person.play()
person.age = 22
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute

if __name__ == '__main__':
main()

_slots_

Python是一门动态语言,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。

限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用

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
class Person(object):

# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')

def __init__(self, name, age):
self._name = name
self._age = age

@property
def name(self):
return self._name

@property
def age(self):
return self._age

@age.setter
def age(self, age):
self._age = age

def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)


def main():
person = Person('王大锤', 22)
person.play()
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True

静态方法和类方法

@staticmethod 静态方法

1
2
3
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b

类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象)

1
2
3
4
@classmethod
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)

抽象类

Python从语法层面并没有像JavaC#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果

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
from abc import ABCMeta, abstractmethod

class Pet(object, metaclass=ABCMeta):
"""宠物"""

def __init__(self, nickname):
self._nickname = nickname

@abstractmethod
def make_voice(self):
"""发出声音"""
pass

class Dog(Pet):
"""狗"""

def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)

class Cat(Pet):
"""猫"""

def make_voice(self):
print('%s: 喵...喵...' % self._nickname)

def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()

if __name__ == '__main__':
main()

文件和异常

将那些在运行时可能会出现状况的代码放在try代码块中,在try代码块的后面可以跟上一个或多个except来捕获可能出现的异常状况,避免执行时程序的异常退出。

1
2
3
4
5
6
try:
x = int(input("First number:"))
y = int(input("Second number:"))
print(x / y)
except:
print("There was an error!")

First number:10
Second number:0
There was an error!

如果不用try except 除数为0时会直接终止程序,精准捕获错误类型

1
2
3
4
5
6
7
8
try:
x = int(input("First number:"))
y = int(input("Second number:"))
print(x / y)
except ValueError:
print("Please enter a valid number next time!")
except ZeroDivisionError:
print("Cannot divide by zeor!")
1
2
3
4
5
6
7
First number:10
Second number0
Cannot divide by zeor!

================= RESTART: /Users/jack/Desktop/python/hello.py =================
First number:text
Please enter a valid number next time!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def main():
f = None
try:
f = open('致橡树.txt', 'r', encoding='utf-8')
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
finally:
if f:
f.close()

if __name__ == '__main__':
main()

读写json文件

把一个列表或者一个字典中的数据保存到文件中又该怎么做呢?答案是将数据以JSON格式进行保存,json应用于跨平台跨语言的数据交换,原因很简单,因为JSON也是纯文本,任何系统任何编程语言处理纯文本都是没有问题的

json模块主要有四个比较重要的函数,分别是:

  • dump - 将Python对象按照JSON格式序列化到文件中
  • dumps - 将Python对象处理成JSON格式的字符串
  • load - 将文件中的JSON数据反序列化成对象
  • loads - 将字符串的内容反序列化成Python对象

进程和线程

Python既支持多进程又支持多线程

多进程

Unix和Linux操作系统上提供了fork()系统调用来创建进程,调用fork()函数的是父进程,创建出的是子进程

fork()函数非常特殊它会返回两次,父进程中可以通过fork()函数的返回值得到子进程的PID,而子进程中的返回值永远都是0

由于Windows系统没有fork()调用,因此要实现跨平台的多进程编程,可以使用multiprocessing模块的Process类来创建子进程

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
rom multiprocessing import Process
from os import getpid
from random import randint
from time import time, sleep


def download_task(filename):
print('启动下载进程,进程号[%d].' % getpid())
print('开始下载%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))


def main():
start = time()
p1 = Process(target=download_task, args=('Python从入门到住院.pdf', ))
p1.start()
p2 = Process(target=download_task, args=('Peking Hot.avi', ))
p2.start()
p1.join()
p2.join()
end = time()
print('总共耗费了%.2f秒.' % (end - start))


if __name__ == '__main__':
main()

也可以使用subprocess模块中的类和函数来创建和启动子进程,然后通过管道来和子进程通信

两个进程间的通信

使用multiprocessing模块中的Queue类,它是可以被多个进程共享的队列,底层是通过管道和信号量(semaphore)机制来实现的

多线程

多线程开发推荐使用threading模块

Python的多线程并不能发挥CPU的多核特性,之所以如此,是因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,这是一个历史遗留问题

多进程还是多线程

是否采用多任务的第二个考虑是任务的类型,可以把任务分为计算密集型和I/O密集型

计算密集型任务由于主要消耗CPU资源,这类任务用Python这样的脚本语言去执行效率通常很低,最能胜任这类任务的是C语言,Python中有嵌入C/C++代码的机制。

网络、存储介质I/O的任务都可以视为I/O密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待I/O操作完成(因为I/O的速度远远低于CPU和内存的速度),对于I/O密集型任务,如果启动多任务,就可以减少I/O等待时间从而让CPU高效率的运转

单线程 + 异步I/O

现代操作系统对I/O操作的改进中最为重要的就是支持异步I/O。如果充分利用操作系统提供的异步I/O支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型。Nginx就是支持异步I/O的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。用Node.js开发的服务器端程序也使用了这种工作模式,这也是当下并发编程的一种流行方案。

在Python语言中,单线程+异步I/O的编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。协程最大的优势就是极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。协程的第二个优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不用加锁,只需要判断状态就好了,所以执行效率比多线程高很多。如果想要充分利用CPU的多核特性,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

yield关键字

一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行

如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:

1
2
3
>>>from inspect import isgeneratorfunction 
>>> isgeneratorfunction(fab)
True

参考

Python入门到精通
Python 教程