语言元素 变量和类型 整型:支持二进制(如0b100,换算成十进制是4)、八进制(如0o100,换算成十进制是64)、十进制(100)和十六进制(0x100,换算成十进制是256)的表示法。
浮点型:支持科学计数法(如1.23456e2)。
字符串型:字符串是以单引号或双引号括起来的任意文本,比如’hello’和”hello”,字符串还有原始字符串表示法、字节字符串表示法、Unicode字符串表示法,而且可以书写成多行的形式(用三个单引号或三个双引号开头,三个单引号或三个双引号结尾)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 """ 使用type()检查变量的类型 Version: 0.1 Author: 骆昊 """ a = 100 b = 12.345 c = 1 + 5j d = 'hello, world' e = True print (type (a)) print (type (b)) print (type (c)) print (type (d)) print (type (e))
可以使用Python中内置的函数对变量类型进行转换。
int():将一个数值或字符串转换成整数,可以指定进制。 float():将一个字符串转换成浮点数。 str():将指定的对象转换成字符串形式,可以指定编码。 chr():将整数转换成该编码对应的字符串(一个字符)。 ord():将字符串(一个字符)转换成对应的编码(整数)。
变量命名 硬性规则:
变量名由字母(广义的Unicode字符,不包括特殊字符)、数字和下划线构成,数字不能开头。
大小写敏感(大写的a和小写的A是两个不同的变量)。
不要跟关键字(有特殊含义的单词,后面会讲到)和系统保留字(如函数、模块等的名字)冲突。
PEP 8要求:
用小写字母拼写,多个单词用下划线连接。
受保护的实例属性用单个下划线开头。
私有的实例属性用两个下划线开头。
运算符
描述
[]
[:]
下标,切片
**
指数
~
+
-
按位取反, 正负号
*
/
%
//
乘,除,模,整除
+
-
加,减
>>
<<
右移,左移
&
按位与
^
|
按位异或,按位或
<=
<
>
>=
小于等于,小于,大于,大于等于
==
!=
等于,不等于
is
is not
身份运算符
in
not in
成员运算符
not
or
and
逻辑运算符
=
+=
-=
*=
/=
%=
//=
**=
&=
|=
^=
>>=
<<=
(复合)赋值运算符
运算符
描述
实例
&
按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0
(a & b) 输出结果 12 ,二进制解释: 0000 1100
按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。
^
按位异或运算符:当两对应的二进位相异时,结果为1
(a ^ b) 输出结果 49 ,二进制解释: 0011 0001
~
按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1 。~x 类似于 -x-1
(~a ) 输出结果 -61 ,二进制解释: 1100 0011,在一个有符号二进制数的补码形式。
<<
左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0。
a << 2 输出结果 240 ,二进制解释: 1111 0000
>>
右移动运算符:把”>>”左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数
a >> 2 输出结果 15 ,二进制解释: 0000 1111
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 a = 60 b = 13 c = 0 c = a & b; c = a | b; c = a ^ b; c = ~a; c = a << 2 ; c = a >> 2 ;
身份运算符用于比较两个对象的存储单元
is 是判断两个标识符是不是引用自一个对象 x is y, 类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False
注: id() 函数用于获取对象内存地址。
比较运算符和逻辑运算符 在and运算符左边为False的情况下,右边的表达式根本不会执行。
or运算符也是有短路功能的,在它左边的布尔值为True的情况下,右边的表达式根本不会执行。
print(f'{f:.1f}华氏度 = {c:.1f}摄氏度')
其中{f:.1f}
和{c:.1f}
可以先看成是{f}
和{c}
,表示输出时会用变量f
和变量c
的值替换掉这两个占位符,后面的:.1f
表示这是一个浮点数,小数点后保留1位有效数字。
1 2 3 f = float (input ('请输入华氏温度: ' )) c = (f - 32 ) / 1.8 print ('%.1f华氏度 = %.1f摄氏度' % (f, c))
print函数中的字符串%.1f
是一个占位符,稍后会由一个float
类型的变量值替换掉它。同理,如果字符串中有%d
,后面可以用一个int
类型的变量值替换掉它,而%s
会被字符串
的值替换掉。
1 2 3 4 5 6 7 8 9 """ 输入年份 如果是闰年输出True 否则输出False """ year = int (input ('请输入年份: ' )) is_leap = year % 4 == 0 and year % 100 != 0 or \ year % 400 == 0 print (is_leap)
循环结构 break关键字来提前终止循环,需要注意的是break只能终止它所在的那个循环,这一点在使用嵌套的循环结构需要引起注意。除了break之外,还有另一个关键字是continue,它可以用来放弃本次循环后续的代码直接让循环进入下一轮。
sqrt() function is an inbuilt function in Python programming language that returns the square root of any number. Syntax: math.sqrt(x) Parameter: x is any number such that x>=0 Returns: It returns the square root of the number passed in the parameter. Error: When x<0 it does not executes due to a runtime error.
最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个。 两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数。 最小公倍数=两数的乘积/最大公约(因)数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 """ 输入两个正整数计算它们的最大公约数和最小公倍数 """ x = int (input ('x = ' )) y = int (input ('y = ' )) if x > y: x, y = y, x for factor in range (x, 0 , -1 ): if x % factor == 0 and y % factor == 0 : print ('%d和%d的最大公约数是%d' % (x, y, factor)) print ('%d和%d的最小公倍数是%d' % (x, y, x * y // factor)) break
函数和模块的使用 在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持函数的重载
函数重载(英语:function overloading)或方法重载,是某些编程语言(如 C++、C#、Java、Swift、Kotlin 等)具有的一项特性,该特性允许创建多个具有不同实现的同名函数。对重载函数的调用会运行其适用于调用上下文的具体实现,即允许一个函数调用根据上下文执行不同的任务。
例如,doTask() 和 doTask(object o) 是重载函数。调用后者,必须传入一个 object 作为参数,而调用前者时则不需要参数。
用模块管理函数 module1.py
1 2 def foo (): print ('hello, world!' )
module2.py
1 2 def foo (): print ('goodbye, world!' )
test.py
1 2 3 4 5 import module1 as m1import module2 as m2m1.foo() m2.foo()
如果我们导入的模块除了定义函数之外还有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是"__main__"
。
module3.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def foo (): pass def bar (): pass if __name__ == '__main__' : print ('call foo()' ) foo() print ('call bar()' ) bar()
test.py
变量的作用域 Python查找一个变量时会按照“局部作用域”、“嵌套作用域”、“全局作用域”和“内置作用域”的顺序进行搜索.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def foo (): b = 'hello' def bar (): c = True print (a) print (b) print (c) bar() if __name__ == '__main__' : a = 100 foo()
代码的if分支中定义了一个变量a,这是一个全局变量(global variable),属于全局作用域,因为它没有定义在任何一个函数中。在上面的foo函数中我们定义了变量b,这是一个定义在函数中的局部变量(local variable),属于局部作用域,在foo函数的外部并不能访问到它;但对于foo函数内部的bar函数来说,变量b属于嵌套作用域,在bar函数中我们是可以访问到它的。 bar函数中的变量c属于局部作用域,在bar函数之外是无法访问的。
“内置作用域”就是Python内置的那些标识符,我们之前用过的input、print、int等都属于内置作用域。
global
关键字来指示foo函数中的变量a来自于全局作用域,如果全局作用域中没有a,那么下面一行的代码就会定义变量a并将其置于全局作用域。同理,如果我们希望函数内部的函数能够修改嵌套作用域中的变量,可以使用nonlocal
关键字来指示变量来自于嵌套作用域,请大家自行试验。
1 2 3 4 5 6 7 def main (): pass if __name__ == '__main__' : main()
字符串和常用数据结构 在\后面还可以跟一个八进制或者十六进制数来表示字符,例如\141和\x61都代表小写字母a,前者是八进制的表示法,后者是十六进制的表示法。也可以在\后面跟Unicode字符编码来表示字符,例如\u9a86\u660a代表的是中文“骆昊”。
raw string 如果不希望字符串中的\表示转义,我们可以通过在字符串的最前面加上字母r来加以说明
To include the newline character in the string, prefix the string variable with r or R to create a raw string:
raw_s = r'Hi\nHello'
print(raw_s)
The output is:
Hi\nHello
Including Double Backslash Characters in a String Using Raw String
1 2 str2 = 'abc123456' print (str2[::-1 ])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 s1 = 'hello ' * 3 print (s1) s2 = 'world' s1 += s2 print (s1) print ('ll' in s1) print ('good' in s1) str2 = 'abc123456' print (str2[2 ]) print (str2[2 :5 ]) print (str2[2 :]) print (str2[2 ::2 ]) print (str2[::2 ]) print (str2[::-1 ]) print (str2[-3 :-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 26 27 28 29 30 31 32 33 34 35 str1 = 'hello, world!' print (len (str1)) print (str1.capitalize()) print (str1.title()) print (str1.upper()) print (str1.find('or' )) print (str1.find('shit' )) print (str1.startswith('He' )) print (str1.startswith('hel' )) print (str1.endswith('!' )) print (str1.center(50 , '*' ))print (str1.rjust(50 , ' ' ))str2 = 'abc123456' print (str2.isdigit()) print (str2.isalpha()) print (str2.isalnum()) str3 = ' jackfrued@126.com ' print (str3)print (str3.strip())
1 2 3 a, b = 5 , 10 print ('{0} * {1} = {2}' .format (a, b, a * b))print (f'{a} * {b} = {a * b} ' )
常用数据结构 1 2 3 4 5 6 7 8 9 10 list1 = [1 , 3 , 5 , 7 , 100 ] for index in range (len (list1)): print (list1[index]) for elem in list1: print (elem) for index, elem in enumerate (list1): print (index, elem)
向列表中添加元素以及如何从列表中移除元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 list1 = [1 , 3 , 5 , 7 , 100 ] list1.append(200 ) list1.insert(1 , 400 ) list1 += [1000 , 2000 ] print (list1) print (len (list1)) if 3 in list1: list1.remove(3 ) if 1234 in list1: list1.remove(1234 ) print (list1) list1.pop() list1.pop(0 ) list1.pop(len (list1) - 1 ) print (list1) list1.clear() print (list1)
列表的排序操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 list1 = ['orange' , 'apple' , 'zoo' , 'internationalization' , 'blueberry' ] list2 = sorted (list1) list3 = sorted (list1, reverse=True ) list4 = sorted (list1, key=len ) print (list1)print (list2)print (list3)print (list4)list1.sort(reverse=True ) print (list1)
生成式和生成器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import sysf = [x for x in range (1 , 10 )] print (f)f = [x + y for x in 'ABCDE' for y in '1234567' ] print (f)f = [x ** 2 for x in range (1 , 1000 )] print (sys.getsizeof(f)) print (f)f = (x ** 2 for x in range (1 , 1000 )) print (sys.getsizeof(f)) print (f)for val in f: print (val)
通过yield关键字将一个普通函数改造成生成器函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def fib (n ): a, b = 0 , 1 for _ in range (n): a, b = b, a + b yield a def main (): for val in fib(20 ): print (val) if __name__ == '__main__' : main()
1 2 3 4 5 6 temp = a a = b b = temp a, b = b, a
使用集合 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 26 27 28 29 30 31 32 33 34 35 36 37 scores = {'骆昊' : 95 , '白元芳' : 78 , '狄仁杰' : 82 } print (scores)items1 = dict (one=1 , two=2 , three=3 , four=4 ) items2 = dict (zip (['a' , 'b' , 'c' ], '123' )) items3 = {num: num ** 2 for num in range (1 , 10 )} print (items1, items2, items3)print (scores['骆昊' ])print (scores['狄仁杰' ])for key in scores: print (f'{key} : {scores[key]} ' ) scores['白元芳' ] = 65 scores['诸葛王朗' ] = 71 scores.update(冷面=67 , 方启鹤=85 ) print (scores)if '武则天' in scores: print (scores['武则天' ]) print (scores.get('武则天' ))print (scores.get('武则天' , 60 ))print (scores.popitem())print (scores.popitem())print (scores.pop('骆昊' , 100 ))scores.clear() print (scores)
index = pos if has_dot else pos + 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def get_suffix (filename, has_dot=False ): """ 获取文件名的后缀名 :param filename: 文件名 :param has_dot: 返回的后缀名是否需要带点 :return: 文件的后缀名 """ pos = filename.rfind('.' ) if 0 < pos < len (filename) - 1 : index = pos if has_dot else pos + 1 return filename[index:] else : return ''
面向对象编程基础 属性到底具有怎样的访问权限(也称为可见性)。因为在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。
在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Test : def __init__ (self, foo ): self.__foo = foo def __bar (self ): print (self.__foo) print ('__bar' ) def main (): test = Test('hello' ) test.__bar() 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()
不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻
封装 封装的理解是”隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口”
面向对象进阶 通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@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 36 37 38 class Person (object ): 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('王大锤' , 12 ) person.play() person.age = 22 person.play() 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 ): __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 = '男'
静态方法和类方法 先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法 来解决这类问题
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 from math import sqrtclass Triangle (object ): def __init__ (self, a, b, c ): self._a = a self._b = b self._c = c @staticmethod def is_valid (a, b, c ): return a + b > c and b + c > a and a + c > b def perimeter (self ): return self._a + self._b + self._c def area (self ): half = self.perimeter() / 2 return sqrt(half * (half - self._a) * (half - self._b) * (half - self._c)) def main (): a, b, c = 3 , 4 , 5 if Triangle.is_valid(a, b, c): t = Triangle(a, b, c) print (t.perimeter()) print (t.area()) else : print ('无法构成三角形.' ) if __name__ == '__main__' : main()
在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象
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 from time import time, localtime, sleepclass Clock (object ): """数字时钟""" def __init__ (self, hour=0 , minute=0 , second=0 ): self._hour = hour self._minute = minute self._second = second @classmethod def now (cls ): ctime = localtime(time()) return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec) def run (self ): """走字""" self._second += 1 if self._second == 60 : self._second = 0 self._minute += 1 if self._minute == 60 : self._minute = 0 self._hour += 1 if self._hour == 24 : self._hour = 0 def show (self ): """显示时间""" return '%02d:%02d:%02d' % \ (self._hour, self._minute, self._second) def main (): clock = Clock.now() while True : print (clock.show()) sleep(1 ) clock.run() if __name__ == '__main__' : main()
类之间的关系
在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 class Person (object ): """人""" 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 ): print ('%s正在愉快的玩耍.' % self._name) def watch_av (self ): if self._age >= 18 : print ('%s正在观看爱情动作片.' % self._name) else : print ('%s只能观看《熊出没》.' % self._name) class Student (Person ): """学生""" def __init__ (self, name, age, grade ): super ().__init__(name, age) self._grade = grade @property def grade (self ): return self._grade @grade.setter def grade (self, grade ): self._grade = grade def study (self, course ): print ('%s的%s正在学习%s.' % (self._grade, self._name, course)) class Teacher (Person ): """老师""" def __init__ (self, name, age, title ): super ().__init__(name, age) self._title = title @property def title (self ): return self._title @title.setter def title (self, title ): self._title = title def teach (self, course ): print ('%s%s正在讲%s.' % (self._name, self._title, course)) def main (): stu = Student('王大锤' , 15 , '初三' ) stu.study('数学' ) stu.watch_av() t = Teacher('骆昊' , 38 , '砖家' ) t.teach('Python程序设计' ) t.watch_av() if __name__ == '__main__' : main()
用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为里氏替换原则。
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
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 from abc import ABCMeta, abstractmethodclass 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()
将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。
Dog和Cat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。
python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 """ 某公司有三种类型的员工 分别是部门经理、程序员和销售员 需要设计一个工资结算系统 根据提供的员工信息来计算月薪 部门经理的月薪是每月固定15000元 程序员的月薪按本月工作时间计算 每小时150元 销售员的月薪是1200元的底薪加上销售额5%的提成 """ from abc import ABCMeta, abstractmethodclass Employee (object , metaclass=ABCMeta): """员工""" def __init__ (self, name ): """ 初始化方法 :param name: 姓名 """ self._name = name @property def name (self ): return self._name @abstractmethod def get_salary (self ): """ 获得月薪 :return: 月薪 """ pass class Manager (Employee ): """部门经理""" def get_salary (self ): return 15000.0 class Programmer (Employee ): """程序员""" def __init__ (self, name, working_hour=0 ): super ().__init__(name) self._working_hour = working_hour @property def working_hour (self ): return self._working_hour @working_hour.setter def working_hour (self, working_hour ): self._working_hour = working_hour if working_hour > 0 else 0 def get_salary (self ): return 150.0 * self._working_hour class Salesman (Employee ): """销售员""" def __init__ (self, name, sales=0 ): super ().__init__(name) self._sales = sales @property def sales (self ): return self._sales @sales.setter def sales (self, sales ): self._sales = sales if sales > 0 else 0 def get_salary (self ): return 1200.0 + self._sales * 0.05 def main (): emps = [ Manager('刘备' ), Programmer('诸葛亮' ), Manager('曹操' ), Salesman('荀彧' ), Salesman('吕布' ), Programmer('张辽' ), Programmer('赵云' ) ] for emp in emps: if isinstance (emp, Programmer): emp.working_hour = int (input ('请输入%s本月工作时间: ' % emp.name)) elif isinstance (emp, Salesman): emp.sales = float (input ('请输入%s本月销售额: ' % emp.name)) print ('%s本月工资为: ¥%s元' % (emp.name, emp.get_salary())) if __name__ == '__main__' : main()
图形用户界面和游戏开发 基本上使用tkinter来开发GUI应用需要以下5个步骤:
导入tkinter模块中我们需要的东西。
创建一个顶层窗口对象并用它来承载整个GUI应用。
在顶层窗口对象上添加GUI组件。
通过代码将这些GUI组件的功能组织起来。
进入主事件循环(main loop)。
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 import tkinterimport tkinter.messageboxdef main (): flag = True def change_label_text (): nonlocal flag flag = not flag color, msg = ('red' , 'Hello, world!' )\ if flag else ('blue' , 'Goodbye, world!' ) label.config(text=msg, fg=color) def confirm_to_quit (): if tkinter.messagebox.askokcancel('温馨提示' , '确定要退出吗?' ): window.quit() window = tkinter.Tk() window.geometry('240x160' ) window.title('小游戏' ) label = tkinter.Label(window, text='Hello, world!' , font='Arial -32' , fg='red' ) label.pack(expand=1 ) panel = tkinter.Frame(window) button1 = tkinter.Button(panel, text='修改' , command=change_label_text) button1.pack(side='left' ) button2 = tkinter.Button(panel, text='退出' , command=confirm_to_quit) button2.pack(side='right' ) panel.pack(side='bottom' ) tkinter.mainloop() if __name__ == '__main__' : main()
文件和异常
操作模式
具体含义
'r'
读取 (默认)
'w'
写入(关闭文件之后,会先截断/删除之前的内容)
'x'
写入,如果文件已经存在会产生异常
'a'
追加,将内容写入到已有文件的末尾
'b'
二进制模式
't'
文本模式(默认)
'+'
更新(既可以读又可以写)
如果open函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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()
调用了sys模块的exit函数退出Python环境,finally块都会被执行,因为exit函数实质上是引发了SystemExit异常)
由于finally块的代码不论程序正常还是异常都会执行到,因此我们通常把finally块称为“总是执行代码块”,它最适合用来做释放外部资源的操作。如果不愿意在finally代码块中关闭文件对象释放资源,也可以使用上下文语法,通过with关键字指定文件对象的上下文环境并在离开上下文环境时自动释放文件资源,代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def main (): try : with open ('致橡树.txt' , 'r' , encoding='utf-8' ) as f: print (f.read()) except FileNotFoundError: print ('无法打开指定的文件!' ) except LookupError: print ('指定了未知的编码!' ) except UnicodeDecodeError: print ('读取文件时解码错误!' ) if __name__ == '__main__' : main()
除了使用文件对象的read方法读取文件之外,还可以使用for-in循环逐行读取或者用readlines方法将文件按行读取到一个列表容器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import timedef main (): with open ('致橡树.txt' , 'r' , encoding='utf-8' ) as f: print (f.read()) with open ('致橡树.txt' , mode='r' ) as f: for line in f: print (type (line)) print (line, end='' ) time.sleep(0.5 ) print () with open ('致橡树.txt' ) as f: lines = f.readlines() print (lines) if __name__ == '__main__' : main()
JSON
JSON
Python
object
dict
array
list
string
str
number (int / real)
int / float
true / false
True / False
null
None
json模块主要有四个比较重要的函数,分别是:
dump - 将Python对象按照JSON格式序列化到文件中
dumps - 将Python对象处理成JSON格式的字符串
load - 将文件中的JSON数据反序列化成对象
loads - 将字符串的内容反序列化成Python对象
序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)
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 import jsondef main (): mydict = { 'name' : '骆昊' , 'age' : 38 , 'qq' : 957658 , 'friends' : ['王大锤' , '白元芳' ], 'cars' : [ {'brand' : 'BYD' , 'max_speed' : 180 }, {'brand' : 'Audi' , 'max_speed' : 280 }, {'brand' : 'Benz' , 'max_speed' : 320 } ] } try : with open ('data.json' , 'w' , encoding='utf-8' ) as fs: json.dump(mydict, fs) except IOError as e: print (e) print ('保存数据完成!' ) if __name__ == '__main__' : main()
re
函数
说明
compile(pattern, flags=0)
编译正则表达式返回正则表达式对象
match(pattern, string, flags=0)
用正则表达式匹配字符串 成功返回匹配对象 否则返回None
search(pattern, string, flags=0)
搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None
fullmatch(pattern, string, flags=0)
match函数的完全匹配(从字符串开头到结尾)版本
findall(pattern, string, flags=0)
查找字符串所有与正则表达式匹配的模式 返回字符串的列表
finditer(pattern, string, flags=0)
查找字符串所有与正则表达式匹配的模式 返回一个迭代器
purge()
清除隐式编译的正则表达式的缓存
re.I / re.IGNORECASE
忽略大小写匹配标记
re.M / re.MULTILINE
多行匹配标记
进程和线程 进程就是操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据,操作系统管理所有进程的执行,为它们合理的分配资源。进程可以通过fork或spawn的方式来创建新的进程来执行其他的任务,不过新的进程也有自己独立的内存空间,因此必须通过进程间通信机制(IPC,Inter-Process Communication)来实现数据共享,具体的方式包括管道、信号、套接字、共享内存区等。
一个进程还可以拥有多个并发的执行线索,简单的说就是拥有多个可以获得CPU调度的执行单元,这就是所谓的线程。由于线程在同一个进程下,它们可以共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易。 在单核CPU系统中,真正的并发是不可能的,因为在某个时刻能够获得CPU的只有唯一的一个线程,多个线程共享了CPU的执行时间。 在其他进程的角度,多线程的程序对其他程序并不友好,因为它占用了更多的CPU执行时间,导致其他程序无法获得足够的CPU执行时间;
多进程 程序中的代码只能按顺序一点点的往下执行,那么即使执行两个毫不相关的下载任务,也需要先等待一个文件下载完成后才能开始下一个下载任务,很显然这并不合理也没有效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from random import randintfrom time import time, sleepdef download_task (filename ): print ('开始下载%s...' % filename) time_to_download = randint(5 , 10 ) sleep(time_to_download) print ('%s下载完成! 耗费了%d秒' % (filename, time_to_download)) def main (): start = time() download_task('Python从入门到住院.pdf' ) download_task('Peking Hot.avi' ) end = time() print ('总共耗费了%.2f秒.' % (end - start)) if __name__ == '__main__' : main()
开始下载Python从入门到住院.pdf… Python从入门到住院.pdf下载完成! 耗费了6秒 开始下载Peking Hot.avi… Peking Hot.avi下载完成! 耗费了7秒 总共耗费了13.01秒.
通过Process类创建了进程对象,通过target参数我们传入一个函数来表示进程启动后要执行的代码,后面的args是一个元组,它代表了传递给函数的参数。Process对象的start方法用来启动进程,而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 25 26 27 28 from multiprocessing import Processfrom os import getpidfrom random import randintfrom time import time, sleepdef 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()
启动下载进程,进程号[1530]. 开始下载Python从入门到住院.pdf… 启动下载进程,进程号[1531]. 开始下载Peking Hot.avi… Peking Hot.avi下载完成! 耗费了7秒 Python从入门到住院.pdf下载完成! 耗费了10秒 总共耗费了10.01秒.
多线程 直接使用threading模块的Thread类来创建线程
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 from random import randintfrom threading import Threadfrom time import time, sleepdef download (filename ): print ('开始下载%s...' % filename) time_to_download = randint(5 , 10 ) sleep(time_to_download) print ('%s下载完成! 耗费了%d秒' % (filename, time_to_download)) def main (): start = time() t1 = Thread(target=download, args=('Python从入门到住院.pdf' ,)) t1.start() t2 = Thread(target=download, args=('Peking Hot.avi' ,)) t2.start() t1.join() t2.join() end = time() print ('总共耗费了%.3f秒' % (end - start)) if __name__ == '__main__' : main()
通过继承Thread类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。代码如下所示。
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 random import randintfrom threading import Threadfrom time import time, sleepclass DownloadTask (Thread ): def __init__ (self, filename ): super ().__init__() self._filename = filename def run (self ): print ('开始下载%s...' % self._filename) time_to_download = randint(5 , 10 ) sleep(time_to_download) print ('%s下载完成! 耗费了%d秒' % (self._filename, time_to_download)) def main (): start = time() t1 = DownloadTask('Python从入门到住院.pdf' ) t1.start() t2 = DownloadTask('Peking Hot.avi' ) t2.start() t1.join() t2.join() end = time() print ('总共耗费了%.2f秒.' % (end - start)) if __name__ == '__main__' : main()
如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。
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 from time import sleepfrom threading import Threadclass Account (object ): def __init__ (self ): self._balance = 0 def deposit (self, money ): new_balance = self._balance + money sleep(0.01 ) self._balance = new_balance @property def balance (self ): return self._balance class AddMoneyThread (Thread ): def __init__ (self, account, money ): super ().__init__() self._account = account self._money = money def run (self ): self._account.deposit(self._money) def main (): account = Account() threads = [] for _ in range (100 ): t = AddMoneyThread(account, 1 ) threads.append(t) t.start() for t in threads: t.join() print ('账户余额为: ¥%d元' % account.balance) if __name__ == '__main__' : main()
通过“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。
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 51 from time import sleepfrom threading import Thread, Lockclass Account (object ): def __init__ (self ): self._balance = 0 self._lock = Lock() def deposit (self, money ): self._lock.acquire() try : new_balance = self._balance + money sleep(0.01 ) self._balance = new_balance finally : self._lock.release() @property def balance (self ): return self._balance class AddMoneyThread (Thread ): def __init__ (self, account, money ): super ().__init__() self._account = account self._money = money def run (self ): self._account.deposit(self._money) def main (): account = Account() threads = [] for _ in range (100 ): t = AddMoneyThread(account, 1 ) threads.append(t) t.start() for t in threads: t.join() print ('账户余额为: ¥%d元' % account.balance) if __name__ == '__main__' : main()
Python的多线程并不能发挥CPU的多核特性
任务的类型,可以把任务分为计算密集型和I/O密集型 。 计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如对视频进行编码解码或者格式转换等等 计算密集型任务由于主要消耗CPU资源,这类任务用Python这样的脚本语言去执行效率通常很低,最能胜任这类任务的是C语言
涉及到网络、存储介质I/O的任务都可以视为I/O密集型任务.这类任务的特点是CPU消耗很少,任务的大部分时间都在等待I/O操作完成(因为I/O的速度远远低于CPU和内存的速度) 网络应用和Web应用。
单线程+异步I/O 充分利用操作系统提供的异步I/O支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型。
Nginx就是支持异步I/O的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。
在Python语言中,单线程+异步I/O的编程模型称为协程,基于事件驱动编写高效的多任务程序.
协程最大的优势就是极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。协程的第二个优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不用加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
如果想要充分利用CPU的多核特性,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
网络编程 协议族就是一系列的协议及其构成的通信模型.
TCP/IP模型。与国际标准化组织发布的OSI/RM这个七层模型不同,TCP/IP是一个四层模型,也就是说,该模型将我们使用的网络从逻辑上分解为四个层次,自底向上依次是:网络接口层、网络层、传输层和应用层,如下图所示。
IP通常被翻译为网际协议,它服务于网络层,主要实现了寻址和路由的功能。 “路由器”的网络中继设备,它们会存储转发我们发送到网络上的数据分组,让从源头发出的数据最终能够找到传送到目的地通路,这项功能就是所谓的路由。
TCP全称传输控制协议,它是基于IP提供的寻址和路由服务而建立起来的负责实现端到端可靠传输的协议.
数据不传丢不传错(利用握手、校验和重传机制可以实现)。
流量控制(通过滑动窗口匹配数据发送者和接收者之间的传输速度)。
拥塞控制(通过RTT时间以及对滑动窗口的控制缓解网络拥堵)。
网络应用模式 C/S模式和B/S模式。这里的C指的是Client(客户端),通常是一个需要安装到某个宿主操作系统上的应用程序;而B指的是Browser(浏览器),它几乎是所有图形化操作系统都默认安装了的一个应用软件;通过C或B都可以实现对S(服务器)的访问。 去中心化的网络应用通常没有固定的服务器或者固定的客户端,所有应用的使用者既可以作为资源的提供者也可以作为资源的访问者。
requests 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 from time import timefrom threading import Threadimport requestsclass DownloadHanlder (Thread ): def __init__ (self, url ): super ().__init__() self.url = url def run (self ): filename = self.url[self.url.rfind('/' ) + 1 :] resp = requests.get(self.url) with open ('/Users/Hao/' + filename, 'wb' ) as f: f.write(resp.content) def main (): resp = requests.get( 'http://api.tianapi.com/meinv/?key=APIKey&num=10' ) data_model = resp.json() for mm_dict in data_model['newslist' ]: url = mm_dict['picUrl' ] DownloadHanlder(url).start() if __name__ == '__main__' : main()
套接字编程 socket 套接字就是一套用C语言写成的应用程序开发库,主要用于实现进程间通信和网络编程,在网络应用开发中被广泛使用。
基于套接字来使用传输层提供的传输服务,并基于此开发自己的网络应用。实际开发中使用的套接字可以分为三类:流套接字(TCP套接字)、数据报套接字和原始套接字。
TCP套接字 在Python中可以通过创建socket对象并指定type属性为SOCK_STREAM来使用TCP套接字。 一台主机可能拥有多个IP地址,而且很有可能会配置多个不同的服务,所以作为服务器端的程序,需要在创建套接字对象后将其绑定到指定的IP地址和端口上。
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 socket import socket, SOCK_STREAM, AF_INETfrom datetime import datetimedef main (): server = socket(family=AF_INET, type =SOCK_STREAM) server.bind(('127.0.0.1' , 6789 )) server.listen(512 ) print ('服务器启动开始监听...' ) while True : client, addr = server.accept() print (str (addr) + '连接到了服务器.' ) client.send(str (datetime.now()).encode('utf-8' )) client.close() if __name__ == '__main__' : main()
运行服务器程序后我们可以通过Windows系统的telnet来访问该服务器,结果如下图所示
通过Python的程序来实现TCP客户端的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from socket import socketdef main (): client = socket() client.connect(('127.0.0.1' , 6789 )) print (client.recv(1024 ).decode('utf-8' )) client.close() if __name__ == '__main__' : main()
使用多线程技术处理多个用户请求的服务器,该服务器会向连接到服务器的客户端发送一张图片。
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 from socket import socket, SOCK_STREAM, AF_INETfrom base64 import b64encodefrom json import dumpsfrom threading import Threaddef main (): class FileTransferHandler (Thread ): def __init__ (self, cclient ): super ().__init__() self.cclient = cclient def run (self ): my_dict = {} my_dict['filename' ] = 'guido.jpg' my_dict['filedata' ] = data json_str = dumps(my_dict) self.cclient.send(json_str.encode('utf-8' )) self.cclient.close() server = socket() server.bind(('192.168.1.2' , 5566 )) server.listen(512 ) print ('服务器启动开始监听...' ) with open ('guido.jpg' , 'rb' ) as f: data = b64encode(f.read()).decode('utf-8' ) while True : client, addr = server.accept() FileTransferHandler(client).start() if __name__ == '__main__' : main()
client:
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 from socket import socketfrom json import loadsfrom base64 import b64decodedef main (): client = socket() client.connect(('192.168.1.2' , 5566 )) in_data = bytes () data = client.recv(1024 ) while data: in_data += data data = client.recv(1024 ) my_dict = loads(in_data.decode('utf-8' )) filename = my_dict['filename' ] filedata = my_dict['filedata' ].encode('utf-8' ) with open ('/Users/Hao/' + filename, 'wb' ) as f: f.write(b64decode(filedata)) print ('图片已保存.' ) if __name__ == '__main__' : main()
JSON并不能携带二进制数据,因此对图片的二进制数据进行了Base64编码的处理。Base64是一种用64个字符表示所有二进制数据的编码方式,通过将二进制数据每6位一组的方式重新组织,刚好可以使用0~9的数字、大小写字母以及“+”和“/”总共64个字符表示从000000到111111的64种状态。
udp 不对传输的可靠性和可达性做出任何承诺从而避免了TCP中握手和重传的开销,所以在强调性能和而不是数据完整性的场景中(例如传输网络音视频数据),UDP可能是更好的选择
email 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from smtplib import SMTPfrom email.header import Headerfrom email.mime.text import MIMETextdef main (): sender = 'abcdefg@126.com' receivers = ['uvwxyz@qq.com' , 'uvwxyz@126.com' ] message = MIMEText('用Python发送邮件的示例代码.' , 'plain' , 'utf-8' ) message['From' ] = Header('王大锤' , 'utf-8' ) message['To' ] = Header('骆昊' , 'utf-8' ) message['Subject' ] = Header('示例代码实验邮件' , 'utf-8' ) smtper = SMTP('smtp.126.com' ) smtper.login(sender, 'secretpass' ) smtper.sendmail(sender, receivers, message.as_string()) print ('邮件发送完成!' ) if __name__ == '__main__' : main()
进阶 数据结构和算法
迭代器/生成器
并发编程