0%

内容概要

  • 绑定方法与非绑定方法
  • 非绑定方法

内容详细

绑定方法与非绑定方法

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
类中定义的函数分为两大类:
1 绑定方法
2 非绑定方法

其中绑定方法又分为两种:
1.1 绑定到对象的对象方法
1.2 绑定到类的类方法

在类中正常定义的函数默认是绑定到对象的,而为某个函数加上装饰器@classmethod后,该函数就绑定到了类

类方法通常用来在__init__的基础上提供额外的初始实例化的方式

# 配置文件settings.py的内容
HOST='127.0.0.1'
PORT=3306

# 类方法的应用
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod
def from_conf(cls): # 从配置文件中读取配置进行初始化
return cls(settings.HOST,settings.PORT)

>>> MySQL.from_conf # 绑定到类的方法
<bound method MySQL.from_conf of <class__main__.MySQL'>>
>>> conn=MySQL.from_conf() # 调用类方法,自动将类MySQL当作第一个参数传给cls

绑定到类的方法就是专门给类用的,(虽然对象也可以调用,只不过自动传入的第一个参数仍然是类,也就是说这种调用时没有意义的,且容易引起混淆)

ps:这也是python的对象系统与其他面向对象语言对象系统的区别之一,比如Smalltalk和Ruby中,绑定到类的方法与绑定到对象的方法是严格区分开的。

非绑定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法

该方法不与类或对象绑定,类与对象都可以来调用它,但它就是一个普通函数而已,没有自动传值的功能

import uuid
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id():
return uuid.uuid1()

>>> conn=MySQL(‘127.0.0.1',3306)
>>> print(conn.id) #100365f6-8ae0-11e7-a51e-0088653ea1ec

# 类或对象来调用create_id发现都是普通函数,而非绑定到谁的方法
>>> MySQL.create_id
<function MySQL.create_id at 0x1025c16a8>
>>> conn.create_id
<function MySQL.create_id at 0x1025c16a8>

总结

若类中需要一个功能,该功能的实现代码中需要引用对象则将其定义成对象方法,需要引用类则将其定义成类方法,无需引用类或对象则将其定义成静态方法

机器语言

机器语言是站在计算机(奴隶)的角度,说计算机能听懂/理解的语言,而计算机能直接理解的就是二进制指令,所以机器语言就是直接用二进制编程,这意味着机器语言是直接操作硬件的,因此机器语言属于低级语言,此处的低级指的是底层、贴近计算机硬件

总结
1、执行效率最高
编写的程序可以被计算机无障碍理解、直接运行,执行效率高 。

 **2、开发效率最低**
    **复杂,开发效率低**

 **3、跨平台性差**
    **贴近\依赖具体的硬件,跨平台性差**

汇编语言

汇编语言仅仅是用一个英文标签代表一组二进制指令,毫无疑问,比起机器语言,汇编语言是一种进步,但汇编语言的本质仍然是直接操作硬件,因此汇编语言仍是比较低级/底层的语言、贴近计算机硬件

总结
1、执行效率高
相对于机器语言,使用英文标签编写程序相对简单,执行效率高,但较之机器语言稍低,

2、开发效率低:
仍然是直接操作硬件,比起机器语言来说,复杂度稍低,但依旧居高不下,所以开发效率依旧较低

     **3、跨平台性差**
**同样依赖具体的硬件,跨平台性差**

高级语言

1
2
3
    高级语言是站在人(奴隶主)的角度,说人话,即用人类的字符去编写程序,而人类的字符是在向操作系统发送指令,而非直接操作硬件,所以高级语言是与操作系统打交道的,此处的高级指的是高层、开发者无需考虑硬件细节,因而开发效率可以得到极大的提升,但正因为高级语言离硬件较远,更贴近人类语言,人类可以理解,而计算机则需要通过翻译才能理解,所以执行效率会低于低级语言。

按照翻译的方式的不同,高级语言又分为两种:

编译型(如C语言)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 1、执行效率高
编译是指在应用源程序执行之前,就将程序源代码“翻译”成目标代码(即机器语言),
因此其目标程序可以脱离其语言环境独立执行,使用比较方便,执行效率较高。

2、开发效率低:
应用程序一旦需要修改,必须先修改源代码,然后重新编译、生成新的目标文件才能执行,
而在只有目标文件而没有源代码,修改会很不方便。所以开发效率低于解释型

3、跨平台性差
编译型代码是针对某一个平台翻译的,当前平台翻译的结果无法拿到不同的平台使用,针对不同的平台必须重新编译,即跨平台性差

其他
现在大多数的编程语言都是编译型的。
编译程序将源程序翻译成目标程序后保存在另一个文件中,该目标程序可脱离编译程序直接在计算机上多次运行。
大多数软件产品都是以目标程序形式发行给用户的,不仅便于直接运行,同时又使他人难于盗用其中的技术。
C、C++、Ada、Pascal都是编译实现的

解释型(如python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 1、执行效率低
解释型语言的实现中,翻译器并不产生目标机器代码,而是产生易于执行的中间代码。
这种中间代码与机器代码是不同的,中间代码的解释是由软件支持的,不能直接使用硬件,
软件解释器通常会导致执行效率较低。

2、开发效率高
用解释型语言编写的程序是由另一个可以理解中间代码的解释程序执行的,与编译程序不同的是,
解释程序的任务是逐一将源程序的语句解释成可执行的机器指令,不需要将源程序翻译成目标代码再执行。
解释程序的优点是当语句出现语法错误时,可以立即引起程序员的注意,而程序员在程序开发期间就能进行校正。


3、跨平台性强
代码运行是依赖于解释器,不同平台有对应版本的解释器,所以解释型的跨平台性强

其他
对于解释型Basic语言,需要一个专门的解释器解释执行Basic程序,每条语句只有在执行时才被翻译,
这种解释型语言每执行一次就翻译一次,因而效率低下。一般地,动态语言都是解释型的,
例如:Tcl、Perl、Ruby、VBScript、JavaScript等

PS(混合型语言)

1
2
    Java是一类特殊的编程语言,Java程序也需要编译,但是却没有直接编译为机器语言,而是编译为字节码,
然后在Java虚拟机上以解释方式执行字节码。

总结

1
2
3
4
5
1、执行效率:机器语言>汇编语言>高级语言(编译型>解释型)

2、开发效率:机器语言<汇编语言<高级语言(编译型<解释型)

3、跨平台性:解释型具有极强的跨平台型

python介绍

1
2
3
4
5
6
7
    谈及python,涉及两层意思,一层代表的是python这门语言的语法风格,另外一层代表的则是专门用来解释该语法风格的应用程序:python解释器。

• python的创始人为吉多·范罗苏姆(Guido van Rossum)。Python这个名字,来自Guido所挚爱的电视剧Monty Python’s Flying Circus,他希望这个新的叫做Python的语言,能符合他的理想:创造一种C和shell之间,语法能够像shell一样简洁,易学易用、可拓展性强,同时兼顾C的强大功能。于是Guido在1989年的圣诞节期间,开始编写能够解释Python语言语法的解释器。

• Python崇尚优美、清晰、简单,是一个优秀并广泛使用的语言。最新的TIOBE排行榜https://www.tiobe.com/tiobe-index/,Python已飙升至世界第三。

• Python可以应用于众多领域,如:人工智能、数据分析、爬虫、金融量化、云计算、WEB开发、自动化运维/测试、游戏开发、网络服务、图像处理等众多领域。目前业内几乎所有大中型互联网企业都在使用Python,如:Youtube、Dropbox、BT、Quora(中国知乎)、豆瓣、知乎、Google、Yahoo!、Facebook、NASA、百度、腾讯、汽车之家、美团等。

Python解释器的发展史

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
Granddaddy of Python web frameworks, Zope 1 was released in 1999

Python 1.0 - January 1994 增加了 lambda, map, filter and reduce.

Python 2.0 - October 16, 2000,加入了内存回收机制,构成了现在Python语言框架的基础

Python 2.4 - November 30, 2004, 同年目前最流行的WEB框架Django 诞生

Python 2.5 - September 19, 2006

Python 2.6 - October 1, 2008

Python 2.7 - July 3, 2010

Python 3.1 - June 27, 2009

Python 3.2 - February 20, 2011

Python 3.3 - September 29, 2012

Python 3.4 - March 16, 2014

Python 3.5 - September 13, 2015

Python 3.6 - 2016-12-23 发布python3.6.0

内容概要

  • 装饰器介绍
  • 装饰器的实现

内容详细:

装饰器介绍

为何要用装饰器

1
2
3
4
5
6
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的

# 对扩展开放:意味着有新的需求或变化时,可以对现有的代码进行扩展,以适应新的情况
# 对修改封闭:意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改

软件包含的所有的功能的源代码以及调用方式,都应该避免修改,否则一旦改错,极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器

什么是装饰器

1
2
3
4
5
6
7
8
9
10
'装饰':代指为被装饰对象添加新的功能
'器':代指器具/工具
装饰器与被装饰的对象均可以是任意可调用对象

# 概括的讲,装饰器的作用就是:在不修改被装饰对象源代码和调用方式的前提下 ---> 为被装饰对象添加额外的功能

装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景
装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用

ps:可调用对象有函数、方法或者类

装饰器的实现

函数装饰器分为:’无参装饰器’和’有参装饰器’两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物

无参装饰器的实现

1
2
3
4
5
6
7
8
9
10
如果想为下述函数添加统计其执行时间的功能

import time

def index():
time.sleep(3)
print('Welcome to the index page’)
return 200

index() #函数执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样:

strat_time = time.time()
index() # 函数执行
stop_time = time.time()
print('run time is %s' %(stop_time-start_time))
考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入

def wrapper(func): # 通过参数接收外部的值
start_time=time.time()
res=func()
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
但之后函数的调用方式都需要统一改成:
wrapper(index)
wrapper(其他函数)
1
2
3
4
5
6
7
8
9
10
11
12
========================================================================================

但这就违反了不能修改被装饰对象调用方式的原则,于是我们换一种为函数体传值的方式,即将值包给函数,如下:

def timer(func):
def wrapper(): # 引用外部作用域的变量func
start_time=time.time()
res=func()
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
1
2
3
4
5

这样我们便可以在不修改被装饰函数源代码和调用方式的前提下,为其添加上统计时间的功能,只不过需要事先执行一次timer将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名/函数名index 如下:

index = timer(index) # 得到index = wrapper,wrapper携带对外作用域的引用:func = 原始的index
index() # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index
1
2
3
4
5
6
7
8
9
10
11
12
13
========================================================================================

至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能
但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常

def home(name):
time.sleep(5)
print('Welcome to the home page',name)

home=timer(home)
home('egon')
#抛出异常
TypeError: wrapper() takes 0 positional arguments but 1 was given
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

========================================================================================

之所以会抛出异常,是因为home('egon')调用的其实是wrapper('egon'),而函数wrapper没有参数
wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况
要使用:*args + **kwargs的组合,于是 ---> 最终版装饰器timer如下:

def timer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
此时我们就可以用timer来装饰带参数或不带参数的函数了,但是为了简洁而优雅地使用装饰器
python提供了专门的装饰器语法来取代index = timer(index)的形式
# 需要在被装饰对象的正上方单独一行添加@timer
当解释器解释到@timer时就会调用timer函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

@timer # index=timer(index)
def index():
time.sleep(3)
print('Welcome to the index page')
return 200
@timer # index=timer(home)• def home(name):
time.sleep(5)
print('Welcome to the home page’,name)
如果我们有多个装饰器,可以叠加多个

@deco3
@deco2
@deco1
def index():
pass
叠加多个装饰器也无特殊之处,上述代码语义如下:

index=deco3(deco2(deco1(index)))

有参装饰器的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在了解了无参装饰器的实现原理之后,我们可以再实现一个用来为被装饰对象添加认证功能的装饰器,实现的基本形式如下:

def deco(func):
def wrapper(*args,**kwargs):
编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
return wrapper
如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下

def deco(func):
def wrapper(*args,**kwargs):
if driver == 'file':
编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
elif driver == 'mysql':
编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
return wrapper
1
2
3
4
5
6
函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能,不能用来接受其他类别的参数,可以再deco的外部再包一层函数auth,用来专门接受额外的参数,这样便保证了再auth函数内无论多少层都可以引用到

def auth(driver):
def deco(func):
……
return deco
1
2
3
4
5
6
7
8
9
10
此时我们就实现了一个有参装饰器,使用方式如下

先调用auth_type(driver='file'),得到@deco,deco是一个闭包函数,
包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样
@auth(driver='file')
def index():
pass
@auth(driver='mysql')
def home():
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释

@timer
def home(name):
'''
home page function
:param name: str
:return: None
'''
time.sleep(5)
print('Welcome to the home page',name)

print(help(home))

打印结果:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None
1
2
3
4
5
6
7
8
9
10
11
12
13

在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,想要保留原函数的文档和函数名属性,需要修正装饰器

def timer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
wrapper.__doc__=func.__doc__
wrapper.__name__=func.__name__
return wrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
按照上述方式来实现保留原函数属性过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下

from functools import wraps

def timer(func):
@wraps(func)
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper

内容概要

  • 生成器与yield
  • yield表达式应用
  • 三元表达式、列表生成式、生成器表达式

内容详细

生成器与yield

1
2
3
4
5
6
7
8
9
10
11
12
若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象

>>> def my_range(start,stop,step=1):
... print('start...')
... while start < stop:
... yield start
... start+=step
... print('end...')
...
>>> g=my_range(0,3)
>>> g
<generator object my_range at 0x104105678>
1
2
3
4
5
6
7

生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器

>>> g.__iter__
<method-wrapper '__iter__' of generator object at 0x1037d2af0>
>>> g.__next__
<method-wrapper '__next__' of generator object at 0x1037d2af0>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

所以我们可以用next(生成器)触发生成器所对应函数的执行

>>> next(g) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
start...
0
>>> next(g) # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
1
>>> next(g) # 周而复始...
2
>>> next(g) # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代
end...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
1
2
3
4
5
6
7
8
9
10
11

既然生成器对象属于迭代器,那么必然可以使用for循环迭代:

>>> for i in countdown(3):
... print(i)
...
countdown start
3
2
1
Done!

有了yield关键字,我们就有了一种自定义迭代器的实现方式
yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield则可以保存函数的运行状态并挂起函数,用来返回多次值

yield表达式应用

1
2
3
4
5
6
7
在函数内可以采用表达式形式的yield

def eater():
print('ready to eat')
while True:
food = yield
print('get the food: %s, and start to eat' % food)
1
2
3
4
5
6
7
8
9
10
11
可以拿到函数的生成器对象持续为函数体send值:

g = eater() # 得到生成器对象
g
>>> <generator object eater at 0x101b6e2b0>
next(g) # 需要事先'初始化'一次,让函数挂起在food = yield,等待调用g.send()方法为其传值
>>> ready to eat
g.send('包子')
>>> get the food: 包子, and start to eat
g.send('鸡腿')
>>> get the food: 鸡腿, and start to eat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'''
针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food = yield的位置,等待调用g.send()方法为函数体传值,g.send(None)等同于next(g)
'''

• 我们可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作,如下

def init(func):
def wrapper(*args,**kwargs):
g=func(*args,**kwargs)
next(g)
return g
return wrapper

@init
def eater():
print('Ready to eat')
while True:
food=yield
print('get the food: %s, and start to eat' %food)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
表达式形式的yield也可以用于返回多次值,即变量名=yield 值的形式,如下

>>> def eater():
... print('Ready to eat')
... food_list=[]
... while True:
... food=yield food_list
... food_list.append(food)
...
>>> e=eater()
>>> next(e)
Ready to eat
[]
>>> e.send('蒸羊羔')
['蒸羊羔']
>>> e.send('蒸熊掌')
['蒸羊羔', '蒸熊掌']
>>> e.send('蒸鹿尾儿')
['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']

三元表达式、列表生成式、生成器表达式

三元表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
三元表达式是python为我们提供的一种简化代码的解决方案,语法如下:

# res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值

针对下述场景:

def max2(x,y):
if x > y:
return x
else:
return y

res = max2(1,2)
用三元表达式可以一行解决

x=1
y=2
# res = x if x > y else y # 三元表达式

列表生成式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
列表生成式是python为我们提供的一种简化代码的解决方案,用来快速生成列表,语法如下:

[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]

#类似于
res=[]
for item1 in iterable1:
if condition1:
for item2 in iterable2:
if condition2
...
for itemN in iterableN:
if conditionN:
res.append(expression)
1
2
3
4
5
6
7
8
针对下述场景:

egg_list=[]
for i in range(10):
egg_list.append('鸡蛋%s' %i)
用列表生成式可以一行解决

# egg_list=['鸡蛋%s' %i for i in range(10)]

生成式表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
创建一个生成器对象有两种方式:
1 调用带yield关键字的函数

2 生成器表达式 ---> 与列表生成式的语法格式相同,只需要将[]换成()即可:
(expression for item in iterable if condition)

对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象

>>> [x*x for x in range(3)]
[0, 1, 4]
>>> g=(x*x for x in range(3))
>>> g
<generator object <genexpr> at 0x101be0ba0>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
对比列表生成式,生成器表达式的优点自然是节省内存(一次只产生一个值在内存中)

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g) #抛出异常StopIteration
如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成

with open('db.txt','rb') as f:
nums=(len(line) for line in f)
total_size=sum(nums) # 依次执行next(nums),然后累加到一起得到结果=

引子

什么是语言?什么是编程语言?为什么要有编程语言?

什么是编程?为什么要编程?

1
2
3
4
5
6
7
8
9
1.1 
1 语言就是人与人之间沟通的介质(英语、汉语...)
2 编程语言就是人与计算机之间沟通的介质
3 为什么要有编程语言,或者说人为什么要与计算机沟通呢?这是因为在编程的世界里,计算机就好比是人的奴隶,人与计算机沟通的目的就是为了奴役计算机,让计算机按照人类的思维逻辑自发地去工作从而把人力解放出来。
1.2
1 编程就是人把自己想命令计算机干的事用编程语言翻译出来并写到文件里(这一系列的文件就是程序)
2 分为两个层面
-为了更好的控制人类的奴隶(计算机),我们需要学习计算机是由什么组成的、它能做什么、它是怎样工作的
-我们需要学习编程语言,从而把原来需要人力来完成的业务(ATM+购物)交给计算机去做

计算机组成原理

什么是计算机?

俗称电脑,即通电的大脑,电脑二字蕴含了人类对计算机的终极期望,希望它能真的像人脑一样去工作,从而解放人力。

懒人奴役的是真正的人,而人是无法不吃、不喝、不睡觉一直工作的,但是计算机作为一台机器是可以做到的,所以把计算机当奴隶是上上之选。

世界是由聪明的懒人统治的,任何时期,总有一群聪明的懒人想要奴隶别人。在奴隶制社会,聪明的懒人奴役的是真正的人,而人是无法不吃、不喝、不睡觉一直工作的,但是计算机作为一台机器是可以做到的,所以把计算机当奴隶是上上之选。

计算机的五大组成部分

控制器

控制器是计算机的指挥系统,用来控制计算机其他组件的运行,相当于人类的大脑

运算器

运算器是计算机的运算功能,用来做算术运算和逻辑运算,相当于人脑。
PS:控制器 + 运算器 = CPU

存储器

存储器是计算机的记忆功能,用来存取数据。

存储器主要分为内存与外存:

内存相当于人的短期记忆。断电数据丢失

外存(如磁盘),相当于记事的本子,断电数据不会丢失,是用来永久保存数据的

ps:内存的存取速度要远远高于外存

输入设备input

输入设备是计算接收外界输入数据的工具,如键盘、鼠标,相当于人的眼睛或耳朵。

输出设备output

输出设备是计算机向外输出数据的工具,如显示器、打印机,相当于人说的话,写出的文章。

ps:存储器如内存、磁盘等既是输入设备又是输出设备,统称为IO设备

操作系统概述

操作系统的由来

大前提:我们编程目的就是为了奴役计算机,让计算机硬件自发地运行起来,然而硬件毕竟是”死的“,硬件的运行都是由软件支配。

倘若我们要开发一个应用程序,比如暴风音影,该软件的一个核心业务就是播放视频,开发者若要编写程序完成播放视频这个业务逻辑,必先涉及到底层硬件硬盘的基本运作(视频文件都是先存放于硬盘中),这意味着开发者在编写业务逻辑代码之前,必须先编写一个控制硬盘基本运行的控制程序,然而这仅仅只是一个开始,事实上,在编写应用程序的业务逻辑前,需要开发者编写出一套完整的控制程序用来控制所有硬件的基本运行(这要求开发者需要详细了解计算机硬件的各种控制细节,例如我们必须把CPU里面所有指令集都掌握一遍),如此,所有的开发者在开发程序时都必须依次开发两种:
  1、编写一套完整的的控制程序,用来控制硬件的基本运行,以及把复杂的硬件的操作封装成简单的接口
  2、基于控制程序的接口开发包含一系列业务逻辑的程序,为了与控制程序区分,可以称为应用程序,以ATM这款应用程序为例,业务逻辑有提款、转账、查询余额等
综上,对于不同公司的开发者来说,应用程序的业务逻辑各不相同,但硬件的控制程序都大致相同,为了避免所有程序员做重复劳动,以及不用再耗费精力去了解所有硬件的运行细节,有公司专门跳出来承担起控制程序的开发任务,这里所说的控制程序指的就是操作系统。

操作系统的功能就是帮我们把复杂的硬件的控制封装成简单的接口,对于开发应用程序来说只需要调用操作系统提供给我们的接口即可

系统软件与应用软件

硬件以上运行的都是软件,而软件分为两类:
一、应用软件(例如qq、word、暴风影音,我们学习python就是为了开发应用软件的)

二、操作系统,操作系统应用软件与硬件之间的一个桥梁,是协调、管理、控制计算机硬件与应用软件资源的控制程序。

计算机系统的三层结构

img

一个非常重要的基础概念:平台

应用程序都是运行于操作系统之上,而操作系统则是运行于硬件之上的,所以承载应用程序的是一台运行有操作系统的计算机,称之为应用程序的运行平台,即:硬件 + 操作系统 == 平台
常见的平台有:windows系统+某款硬件、linux系统+某款硬件、ubuntu+某款硬件等,我们在开发应用程序时就需要考虑到应用程序的跨平台性,如果能开发出一款可以在任意平台运行的应用程序,那对于开发者来说真是极大的福音。而决定应用软件的跨平台性的关键因素往往是编程语言的选择,python恰好是一款跨平台性语言,这也是我们学习它的原因之一。

img

内容概要

  • 软件开发目录规范

内容详细

软件开发目录规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
为了提高程序的可读性与可维护性,我们应该为软件设计良好的目录结构,这与规范的编码风格同等重要。软件的目录规范并无硬性标准,只要清晰可读即可,假设你的软件名为foo,笔者推荐目录结构如下

Foo/
|-- core/
| |-- core.py
|
|-- api/
| |-- api.py
|
|-- db/
| |-- db_handle.py
|
|-- lib/
| |-- common.py
|
|-- conf/
| |-- settings.py
|
|-- run.py
|-- setup.py
|-- requirements.txt
|-- README
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
简要解释一下:

• core/: 存放业务逻辑相关代码

• api/: 存放接口文件,接口主要用于为业务逻辑提供数据操作。

• db/: 存放操作数据库相关文件,主要用于与数据库交互

• lib/: 存放程序中常用的自定义模块

• conf/: 存放配置文件

• run.py: 程序的启动文件,一般放在项目的根目录下,因为在运行时会默认将运行文件所在的文件夹作为sys.path的第一个路径,这样就省去了处理环境变量的步骤

• setup.py: 安装、部署、打包的脚本。

• requirements.txt: 存放软件依赖的外部Python包列表。

• README: 项目说明文件。

除此之外,有一些方案给出了更加多的内容,比如LICENSE.txt,ChangeLog.txt文件等,主要是在项目需要开源时才会用到,请读者自行查阅。

关于README的内容,这个应该是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。它需要说明以下几个事项:

1、软件定位,软件的基本功能;

2、运行代码的方法: 安装环境、启动命令等;

3、简要的使用说明;

4、代码目录结构说明,更详细点可以说明软件的基本原理;

5、常见问题说明。


关于setup.py和requirements.txt:

一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情,这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。

requirements.txt文件的存在是为了方便开发者维护软件的依赖库。我们需要将开发过程中依赖库的信息添加进该文件中,避免在 setup.py安装依赖时漏掉软件包,同时也方便了使用者明确项目引用了哪些Python包。

这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,这样就可以简单的通过 pip install -r requirements.txt来把所有Python依赖库都装好了

内容概要

  • 迭代器介绍
  • for循环原理
  • 迭代器的优缺点

内容详细

迭代器介绍

1
2
3
4
5
6
迭代器即用来迭代取值的工具,而迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果
每一次对工程的重复称为一次'迭代',而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代

while True:
msg = input('>>: ').strip()
print(msg)
1
2
3
4
5
6
7
8
下述while循环才是一个迭代的过程,不仅满足重复,而且'每次重新赋值后的index值作为下一次循环中新的索引进行取值,反复迭代,最终可以取尽列表中的值'

goods = ['mac', 'lenovo', 'acer', 'dell', 'sony']

index = 0
while index < len(goods):
print(goods[index])
index += 1

可迭代对象

1
2
3
4
5
6
7
8
9
10
通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串、列表、元组
但对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式,这就用到了'迭代器'

想了解迭代器是什么,必须事先搞清楚一个很重要的概念:可迭代对象

从语法形式上讲,内置有__inter__方法的对象都是可迭代对象,字符串、列表、元组、字典、集合、打开的文件都是可迭代对象:

{'name':'egon'}.__iter__
{7,8,9}.__iter__
……

迭代器对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
调用obj.iter()方法返回的结果就是一个'迭代器对象'
迭代器对象是内置有iternext方法的对象,打开的文件本身就是一个迭代器对象,执行迭代器对象.iter()方法得到的仍然是迭代器本身,而执行迭代器.next()方法就会计算出迭代器的下一个值
迭代器是python提供的一种统一的、不依赖索引的迭代取值方式,只要存在多个'值',无论序列类型还是非序列类型都可以按照迭代器的方式取值

s = {1,2,3} # 可迭代对象
i = iter(s) # 本质就是在调用s.__iter__(),返回s的迭代器对象i
next(i) # 本质就是在调用i.__next__()
>>> 1
next(i)
>>> 2
next(i)
>>> 3
next(i)
>>> # 抛出stopiteration的异常,代表无值可取,迭代结束

for循环原理

1
2
3
4
5
6
7
8
9
有了迭代器之后 我们便可以不依赖索引迭代取值了,使用while循环的实现方式如下:

goods=['mac','lenovo','acer','dell','sony']
i=iter(goods) #每次都需要重新获取一个迭代器对象
while True:
try:
print(next(i))
except StopIteration: #捕捉异常终止循环
break
1
2
3
4
5
6

for循环又称为迭代循环,in后可以跟任意可迭代对象,上述while循环可以简写为:

goods=['mac','lenovo','acer','dell','sony']
for item in goods:
print(item)

for循环在工作时,首先会调用可迭代对象goods内置的iter方法拿到一个迭代器对象,然后再调用该迭代器对象的next方法将取到的值赋给item,执行循环体完成一次循环,周而复始,直到捕捉stopiteration异常,结束迭代

迭代器的优缺点

基于索引的迭代取值,所有迭代的状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存:

迭代器的优点

1
2
3
1 为序列和非序列类型提供了一种统一的迭代取值方式

2 惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时 刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都 存放于内存中,受内存大小的限制,可以存放的值的个数是有限的

迭代器的缺点

1
2
3
1 除非取尽,否则无法获取迭代器的长度

2 只能取下一个值,不能回到开始,更像是'一次性的',迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值

内容概要

  • 编程范式
  • 面向过程
  • 函数式

内容详解

编程范式

很多初学者在了解了一门编程语言的基本语法和使用后,面对一个开发需求时,仍然会觉得无从下手,没有思路,本节主题’编程范式’正是为了解决该问题,那么到底什么是编程范式呢

编程范式指的就是编程的套路,打个比方:如果把编程的过程比喻为练习武功,那编程范式指的就是武林中的各种流派,而在编程的世界里常见的流派有:面向过程,函数式,面向对象…

强调:’功夫的流派没有高低之分,只有习武的人才有高低之分’,在编程世界里更是这样,各种编程范式在不同的场景下都各有优劣,谁好谁坏不能一概而论

面向过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'面向过程'核心是'过程'二字,'过程'指的是解决问题的步骤 ---> 先干啥 在干啥...

基于面向过程开发程序就好比在设计一条流水线,是一种机械式的思维方式,这正好契合计算机的运行原理:任何程序的执行最终都需要转换成cpu的指令流水按过程调度执行 ---> 无论采用什么语言,无论依据何种编程范式设计出的程序,最终的执行都是过程式的

详细的,若程序一开始是要着手解决一个大的问题,按照过程式的思路就是把这个大的问题分解成很多个小问题或子过程去实现,然后依次调用即可,这极大地降低了程序的复杂度。举例如下:

• 写一个数据远程备份程序,分三步:本地数据打包,上传至云服务器,检测备份文件可用性

import os,time

# 一:基于本章所学,我们可以用函数去实现这一个个的步骤
# 1、本地数据打包
def data_backup(folder):
print("找到备份目录: %s" %folder)
print('正在备份...')
zip_file='/tmp/backup_%s.zip' %time.strftime('%Y%m%d')
print('备份成功,备份文件为: %s' %zip_file)
return zip_file
1
2
3
4
5
6
7
8
9

#2、上传至云服务器
def cloud_upload(file):
print("\nconnecting cloud storage center...")
print("cloud storage connected")
print("upload [%s] to cloud..." %file)
link='https://www.xxx.com/bak/%s' %os.path.basename(file)
print('close connection')
return link
1
2
3
4

#3、检测备份文件可用性
def data_backup_check(link):
print("\n下载文件: %s , 验证文件是否无损..." %link)
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


#二:依次调用
# 步骤一:本地数据打包
zip_file = data_backup(r"/Users/egon/欧美100G高清无码")

# 步骤二:上传至云服务器
link=cloud_upload(zip_file)

# 步骤三:检测备份文件的可用性
data_backup_check(link)

'''
面向过程总结:
1 优点:
将复杂的问题流程化,进而简单化
2 缺点:
程序的可扩展性极差,因为一套流水线或者流程就是用来解决一个问题的
就好比生产汽水的流水线无法生产汽车一样,即便是能,也得是大改,而且改一个组件,与其相关的组件可能都需要修改,比如我们修改了cloud_upload的逻辑,那么依赖其结果才能正常执行的data_backup_check也需要修改,这就造成了连锁反应,而且这一问题会随着程序规模的增大而变得越发的糟糕。
'''

def cloud_upload(file): # 加上异常处理,在出现异常的情况下,没有link返回
try:
print("\nconnecting cloud storage center...")
print("cloud storage connected")
print("upload [%s] to cloud..." %file)
link='https://www.xxx.com/bak/%s' %os.path.basename(file)
print('close connection')
return link
except Exception:
print('upload error')
finally:
print('close connection.....')

def data_backup_check(link): # 加上对参数link的判断
if link:
print("\n下载文件: %s , 验证文件是否无损..." %link)
else:
print('\n链接不存在')
1
2
3
4
5
'''
3 应用场景:
面向过程的程序设计一般用于那些功能一旦实现之后就很少需要改变的场景, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程去实现是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护, 那还是用面向对象最为方便。

'''

函数式

函数式编程并非用函数编程这么简单,而是将计算机的运算视为数学意义上的运算,比起面向过程,函数式更加注重的是执行结果而非执行的过程,代表语言有:Haskell、Erlang。而python并不是一门函数式编程语言,但是仍为我们提供了很多函数式编程好的特性,如lambda,map,reduce,filter

匿名函数与lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1 使用def关键字创建的是有名字的函数

2 使用lambda关键字创建的则是没有名字的函数,即匿名函数

语法:
lambda 参数1, 参数2,...:
expression

举例

# 1、定义
lambda x,y,z:x+y+z

# 等同于
def func(x,y,z):
return x+y+z

# 2、调用
# 方式一:
res=(lambda x,y,z:x+y+z)(1,2,3)

# 方式二:
func=lambda x,y,z:x+y+z # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res=func(1,2,3)
1
2
3
4
5
6
7
8
9

匿名函数与有名函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配合使用,我们以下述字典为例来介绍它

salaries={
'siry':3000,
'tom':7000,
'lili':10000,
'jack':2000
}
1
2
3
4
5
6
要想取得薪水的最大值和最小值,我们可以使用内置函数maxmin(为了方便开发,python解释器已经为我们定义好了一系列常用的功能,称之为内置的函数,我们只需要拿来使用即可)

>>> max(salaries)
'tom'
>>> min(salaries)
'jack'
1
2
3
4
5
6
7
8
9
10
11
12
内置maxmin都支持迭代器协议,工作原理都是迭代字典,取得是字典的键,因而比较的是键的最大和最小值,而我们想要的是比较值的最大值与最小值,于是做出如下改动

# 函数max会迭代字典salaries,每取出一个“人名”就会当做参数传给指定的匿名函数,然后将匿名函数的返回值当做比较依据,最终返回薪资最高的那个人的名字
>>> max(salaries,key=lambda k:salaries[k])
'lili'
# 原理同上
>>> min(salaries,key=lambda k:salaries[k])
'jack'
同理,我们直接对字典进行排序,默认也是按照字典的键去排序的

>>> sorted(salaries)
['jack', 'lili', 'siry', 'tom']

map、reduce、filter

1
2
3
4
5
6
7
8
9
10
11
函数map、reduce、filter都支持迭代器协议,用来处理可迭代对象,我们以一个可迭代对象array为例来介绍它们三个的用法

array=[1,2,3,4,5]
要求一:对array的每个元素做平方处理,可以使用map函数

map函数可以接收两个参数,一个是函数,另外一个是可迭代对象,具体用法如下

>>> res=map(lambda x:x**2,array)
>>> res
<map object at 0x1033f45f8>
>>>
1
2
3
4
解析:map会依次迭代array,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器。

>>> list(res) #使用list可以依次迭代res,取得的值作为列表元素
[1, 4, 9, 16, 25]
1
2
3
4
5
6
7
8
9
10

要求二:对array进行合并操作,比如求和运算,这就用到了reduce函数

reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值

# reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用
>>> from functools import reduce
>>> res=reduce(lambda x,y:x+y,array)
>>> res
15
1
2
3
4
5
6
7
8
9
10
解析:

1 没有初始值,reduce函数会先迭代一次array得到的值作为初始值,作为第一个值数传给x,然后继续迭代一次array得到的值作为第二个值传给y,运算的结果为3

2 将上一次reduce运算的结果作为第一个值传给x,然后迭代一次array得到的结果作为第二个值传给y,依次类推,知道迭代完array的所有元素,得到最终的结果15

也可以为reduce指定初始值

>>> res=reduce(lambda x,y:x+y,array,100)•>>> res
115
1
2
3
4
5
6
7
8
9
10
要求三:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素

>>> res=filter(lambda x:x>3,array)
解析:filter函数会依次迭代array,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。

>>> list(res)
[4, 5]
'''
提示:我们介绍map、filter、reduce只是为了带大家了解函数式编程的大致思想,在实际开发中,我们完全可以用列表生成式或者生成器表达式来实现三者的功能。
'''

内容概要

  • 对象的概念
  • 类与对象
  • 面对对象编程

内容详细

对象的概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'面对对象'的核心就是'对象'二字,而对象的精髓在于'整合'

所有的程序都是由'数据' + '功能'组成,因而编写程序的本质就是:定义出一系列的数据,然后定义出一系列的功能来对数据进行操作

在学习'对象'之前,程序中的数据与功能是分离开的,例如:

# 数据:name、age、sex
name='lili'
age=18
sex='female'

# 功能:tell_info
def tell_info(name,age,sex):
print('<%s:%s:%s>' %(name,age,sex))

# 此时若想执行查看个人信息的功能,需要同时拿来两样东西,一类是功能tell_info,另外一类则是多个数据name、age、sex,然后才能执行,非常麻烦
tell_info(name,age,sex)
1
2
3
4
5
6
7
8
9
10

========================================================================================

在学习了'对象'之后,我们就有了一个容器,该容器可以盛放数据与功能,所以我们可以说:对象是把数据与功能整合到一起的产物,或者说'对象'就是一个盛放数据与功能的容器

如果:
'数据'比喻为'睫毛膏''眼影''唇彩'等化妆所需要的原材料
'功能'比喻为'眼线笔''眉笔'等化妆所需要的工具
那么:
'对象'就是一个'彩妆盒''彩妆盒'可以把'原材料''工具'都装到一起

img

1
2
3
4
5
6
7
如果我们把'化妆'比喻为要执行的业务逻辑,此时只需要拿来一样东西即可,那就是彩妆盒,因为彩妆盒里整合了'化妆'所需的所有原材料与功能

这比起分别拿来原材料与功能才能执行,要方便的多

在了解了对象的基本概念之后,理解面向对象的编程方式就相对简单很多了,面向对象编程就是要造出一个个的对象,把原本分散开的相关数据'整合'到一个个的对象里,这么做既方便使用,也可以提高程序的解耦合程度,进而提升了程序的可扩展性

强调:软件质量属性包含很多方面,面向对象解决的仅仅是扩展性问题

img

类与对象

1
2
3
4
5
类 ---> 类别/种类:是免息那个对象分析和设计的基石
如果多个对象有相似的数据与功能,那么该多个对象就属于同一种类
有了类的好处:
可以把同一类的对象相同的数据与功能存放到类里,而无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可,极大地节省了空间
所以 ---> 如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能的容器

img

1
2
3
综上所述:
1 虽然我们是先介绍对象,后介绍类,但是:'在程序中,必须要事先定义类,然后再调用类产生对象(调用类类拿到的返回值就是对象)'
2 产生对象的类与对象之间存在关联,这种关联指的是:对象可以访问到类中共有的数据与功能,所以类中的内容仍然是属于对象的,类只不过是一种节省空间,减少代码冗余的机制,面对对象编程最终的核心仍然是去使用对象

面对对象编程

类的定义与实例化

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
我们以开发一个清华大学的选课系统为例,来简单介绍基于面向对象的思想如何编写程序

面向对象的基本思路就是把程序中要用到的、相关联的数据与功能整合到对象里,然后再去使用,但程序中要用到的数据以及功能那么多,如何找到相关连的呢?我需要先提取选课系统里的角色:学生、老师、课程等,然后显而易见的是:学生有学生相关的数据于功能,老师有老师相关的数据与功能,我们单以学生为例,

# 学生的数据有
学校
名字
年龄
性别

# 学生的功能有
选课
详细的

# 学生1:
数据:
学校=清华大学
姓名=李建刚
性别=男
年龄=28
功能:
选课

# 学生2:
数据:
学校=清华大学
姓名=王大力
性别=女
年龄=18
功能:
选课

# 学生3:
数据:
学校=清华大学
姓名=牛嗷嗷
性别=男
年龄=38
功能:
选课
我们可以总结出一个学生类,用来存放学生们相同的数据与功能

# 学生类
相同的特征:
学校=清华大学
相同的功能:
选课
1
2
3
4
5
6
7
8
基于上述分析的结果,我们接下来需要做的就是在程序中定义出类,然后调用类产生对象

class Student: # 类的命名应该使用“驼峰体”

school='清华大学' # 数据

def choose(self): # 功能
print('%s is choosing a course' %self.name)
1
2
3
4
类体最常见的是变量的定义和函数的定义,但其实类体可以包含任意Python代码,类体的代码在类定义阶段就会执行,因而会产生新的名称空间用来存放类中定义的名字,可以打印Student.__dict__来查看类这个容器内盛放的东西

>>> print(Student.__dict__)
{..., 'school': '清华大学', 'choose': <function Student.choose at 0x1018a2950>, ...}
1
2
3
4
5
6

调用类的过程称为将类实例化,拿到的返回值就是程序中的对象,或称为一个实例

>>> stu1=Student() # 每实例化一次Student类就得到一个学生对象
>>> stu2=Student()
>>> stu3=Student()
1
2
3
4
5
6
7
8
9
10
11
12
13
如此stu1、stu2、stu3全都一样了(只有类中共有的内容,而没有各自独有的数据),想在实例化的过程中就为三位学生定制各自独有的数据:姓名,性别,年龄,需要我们在类内部新增一个__init__方法,如下

class Student:
school='清华大学'

#该方法会在对象产生之后自动执行,专门为对象进行初始化操作,可以有任意代码,但一定不能返回非None的值
def __init__(self,name,sex,age):
self.name=name
self.sex=sex
self.age=age

def choose(self):
print('%s is choosing a course' %self.name)
1
2
3
4
5
然后我们重新实例出三位学生

>>> stu1=Student('李建刚','男',28)
>>> stu2=Student('王大力','女',18)
>>> stu3=Student('牛嗷嗷','男',38)
1
2
3
4
5
6
单拿stu1的产生过程来分析,调用类会先产生一个空对象stu1,然后将stu1连同调用类时括号内的参数一起传给Student.__init__(stu1,’李建刚’,’男’,28)

def __init__(self, name, sex, age):
self.name = name # stu1.name = '李建刚'
self.sex = sex # stu1.sex = '男'
self.age = age # stu1.age = 28
1
2
3
4
会产生对象的名称空间,同样可以用__dict__查看

>>> stu1.__dict__
{'name': '李建刚', 'sex': '男', 'age': 28}

至此,我们造出了三个对象与一个类,对象存放各自独有的数据,类中存放对象们共有的内容

img

存的目的是为了用,那么如何访问对象或者类中存放的内容呢?

属性访问

类属性与对象属性

1
2
3
4
5
6
7
在类中定义的名字,都是类的属性,细说的话,类有两种属性:数据属性和函数属性,可以通过__dict__访问属性的值,比如Student.__dict__[‘school’],但Python提供了专门的属性访问语法

>>> Student.school # 访问数据属性,等同于Student.__dict__['school']
'清华大学'
>>> Student.choose # 访问函数属性,等同于Student.__dict__['choose']
<function Student.choose at 0x1018a2950>
# 除了查看属性外,我们还可以使用Student.attrib=value(修改或新增属性),用del Student.attrib删除属性。
1
2
3
4
5
6
7
操作对象的属性也是一样

>>> stu1.name # 查看,等同于obj1.__dict__[‘name']
'李建刚'
>>> stu1.course=’python’ # 新增,等同于obj1.__dict__[‘course']='python'
>>> stu1.age=38 # 修改,等同于obj1.__dict__[‘age']=38
>>> del obj1.course # 删除,等同于del obj1.__dict__['course']

属性查找与绑定方法

1
2
3
4
5
6
7
8
9
10
对象的名称空间里只存放着对象独有的属性,而对象们相似的属性是存放于类中的。对象在访问属性时,会优先从对象本身的__dict__中查找,未找到,则去类的__dict__中查找

1、类中定义的变量是类的数据属性,是共享给所有对象用的,指向相同的内存地址

# id都一样
print(id(Student.school)) # 4301108704

print(id(stu1.school)) # 4301108704
print(id(stu2.school)) # 4301108704
print(id(stu3.school)) # 4301108704
1
2
3
4
5
6

2、类中定义的函数是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数

Student.choose(stu1) # 李建刚 is choosing a course
Student.choose(stu2) # 王大力 is choosing a course
Student.choose(stu3) # 牛嗷嗷 is choosing a course
1
2
3
4
5
6
7
但其实类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同

print(id(Student.choose)) # 4335426280

print(id(stu1.choose)) # 4300433608
print(id(stu2.choose)) # 4300433608
print(id(stu3.choose)) # 4300433608
1
2
3
4
5
6

绑定到对象的方法特殊之处在于,绑定给谁就应该由谁来调用,谁来调用,就会将’谁’本身当做第一个参数自动传入(方法__init__也是一样的道理)

stu1.choose() # 等同于Student.choose(stu1)
stu2.choose() # 等同于Student.choose(stu2)
stu3.choose() # 等同于Student.choose(stu3)
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
绑定到不同对象的choose技能,虽然都是选课,但李建刚选的课,不会选给王大力,这正是”绑定“二字的精髓所在。

#注意:绑定到对象方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但命名为self是约定俗成的。



Python中一切皆为对象,且Python3中类与类型是一个概念,因而绑定方法我们早就接触过

#类型list就是类
>>> list
<class 'list'>

#实例化的到3个对象l1,l2,l3
>>> l1=list([1,2,3])
>>> l2=list(['a','b','c'])
>>> l3=list(['x','y'])

#三个对象都有绑定方法append,是相同的功能,但内存地址不同
>>> l1.append
<built-in method append of list object at 0x10b482b48>
>>> l2.append
<built-in method append of list object at 0x10b482b88>
>>> l3.append
<built-in method append of list object at 0x10b482bc8>

#操作绑定方法l1.append(4),就是在往l1添加4,绝对不会将4添加到l2l3
>>> l1.append(4) #等同于list.append(l1,4)
>>> l1
[1,2,3,4]
>>> l2
['a','b','c']
>>> l3
['x','y']

总结

在上述介绍类与对象的使用过程中,我们更多的是站在底层原理的角度去介绍类与对象之间的关联关系,如果只是站在使用的角度,我们无需考虑语法”对象.属性”中”属性”到底源自于哪里,只需要知道是通过对象获取到的就可以了,所以说,对象是一个高度整合的产物,有了对象,我们只需要使用”对象.xxx“的语法就可以得到跟这个对象相关的所有数据与功能,十分方便且解耦合程度极高。

内容概要

  • 函数对象
  • 闭包函数

函数对象

函数对象指的是函数可以被当做 ‘数据’ 来处理,具体可以分为四个方面的使用

函数可以被引用

1
2
3
4
5
def add(x,y):
return x+y
func = add
func(1,2)
>>>3

函数可以作为容器类型的元素

1
2
3
4
5
dic = {'add' : add, 'max' : max}
dic
>>>{'add': <function add at 0x100661e18>, 'max': <built-in function max>}
dic['add'](1,2)
>>> 3

函数可以作为参数传入另一个函数

1
2
3
4
def foo(x,y,func):
return func(x,y)
foo(1,2,add)
>>> 3

函数的返回值也可以是一个函数

1
2
3
4
5
def bar():
return add
func = bar()
func(1,2)
>>> 3

闭包函数

闭与包

1
2
3
4
5
6
7
8
9
10
11
12
13
# 基于函数对象的概念,我们可以将函数返回到任意位置去调用,但作用域的关系是在定义完函数时就已经被确定了的,与函数的调用位置无关

x = 1
def f1():
def f2():
print(x)

return f2

def f3():
x=3
f2=f1() #调用f1()返回函数f2
f2() #需要按照函数定义时的作用关系去执行,与调用位置无关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

f3() #结果为1

也就是说:函数被当做数据处理时,始终以'自带的作用域'为准

若内嵌函数包含对外部函数作用域(而非全局作用域)中变量的引用,那么该'内嵌函数'就是'闭包函数',简称闭包

x=1
def outer():
x=2
def inner():
print(x)
return inner

func=outer()
func() # 结果为2
1
2
3
4
5
6
7

可以通过函数的closure属性,查看到闭包函数所包裹的外部变量

func.__closure__
>>> (<cell at 0x10212af78: int object at 0x10028cca0>,)
func.__closure__[0].cell_contents
>>> 2
1
2
3
4
5

"闭":代表函数是内部的
"包":代表函数外'包裹'着对外层作用域的引用

所以 ---> 无论在哪调用闭包函数,使用的仍然是包裹在其外层的变量

闭包的用途

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
目前为止 我们得到了两种为函数体传值的方式
# 1 :直接将值以参数的形式传入

# 2 :将值包给函数

import requests

#方式一:
def get(url):
return requests.get(url).text

#方式二:
def page(url):
def get():
return requests.get(url).text
return get
1
2
3
4
5
6
7
8
9
10
11
12
13

提示:requests模块是用来模拟浏览器向网站发送请求并将页面内容下载到本地,需要事先安装:pip3 install requests

对比两种方式:
# 方式一:在下载同一页面时需要重复传入url

# 方式二:只需要传一次值,就会得到一个包含指定url的闭包函数,以后调用该闭包函数则无需再传url

# 方式一下载同一页面
get('https://www.python.org')
get('https://www.python.org')
get('https://www.python.org')
……
1
2
3
4
5
6
7

# 方式二下载同一页面
python=page('https://www.python.org')
python()
python()
python()
……

闭包函数的这种特性有时又称为惰性计算。使用将值包给函数的方式,在接下来的装饰器中也将大有用处