本文主要为 Python基础入门笔记(一)内容的补充。html
迭代器是一个能够记住遍历的位置的对象。java
迭代器对象从集合的第一个元素开始访问,直到全部的元素被访问完结束。python
迭代器只能往前不会后退。linux
迭代器有两个基本的方法:iter()
和 next()
,且字符串、列表或元组对象均可用于建立迭代器,迭代器对象可使用常规 for 语句进行遍历,也可使用 next() 函数来遍历。程序员
具体的实例:正则表达式
# 一、字符创建立迭代器对象
str1 = 'jaybo'
iter1 = iter ( str1 )
# 二、list对象建立迭代器
list1 = [1,2,3,4]
iter2 = iter ( list1 )
# 三、tuple(元祖) 对象建立迭代器
tuple1 = ( 1,2,3,4 )
iter3 = iter ( tuple1 )
# for 循环遍历迭代器对象
for x in iter1 :
print ( x , end = ' ' )
print('\n------------------------')
# next() 函数遍历迭代器
while True :
try :
print ( next ( iter3 ) )
except StopIteration :
break
复制代码
最后输出的结果:编程
j a y b o
------------------------
1
2
3
4
复制代码
list(列表)生成式:数据结构
语法为:多线程
[expr for iter_var in iterable]
[expr for iter_var in iterable if cond_expr]
复制代码
第一种语法:首先迭代 iterable 里全部内容,每一次迭代,都把 iterable 里相应内容放到iter_var 中,再在表达式中应用该 iter_var 的内容,最后用表达式的计算值生成一个列表。闭包
第二种语法:加入了判断语句,只有知足条件的内容才把 iterable 里相应内容放到 iter_var 中,再在表达式中应用该 iter_var 的内容,最后用表达式的计算值生成一个列表。
实例,用一句代码打印九九乘法表:
print('\n'.join([' '.join ('%dx%d=%2d' % (x,y,x*y) for x in range(1,y+1)) for y in range(1,10)]))
复制代码
输出结果:
1x1= 1
1x2= 2 2x2= 4
1x3= 3 2x3= 6 3x3= 9
1x4= 4 2x4= 8 3x4=12 4x4=16
1x5= 5 2x5=10 3x5=15 4x5=20 5x5=25
1x6= 6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7= 7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8= 8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9= 9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
复制代码
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不一样的是,生成器是一个返回迭代器的函数,只能用于迭代操做,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程当中,每次遇到 yield 时函数会暂停并保存当前全部的运行信息,返回 yield 的值。并在下一次执行 next() 方法时从当前位置继续运行。
①建立:
生成器的建立:最简单最简单的方法就是把一个列表生成式的 [] 改为 ()
gen= (x * x for x in range(10))
print(gen)
复制代码
输出结果:
generator object at 0x0000000002734A40
复制代码
建立 List 和 generator 的区别仅在于最外层的 [] 和 () 。可是生成器并不真正建立数字列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目“产生” ( yield ) 出来。 生成器表达式使用了“惰性计算” ( lazy evaluation,也有翻译为“延迟求值”,我觉得这种按需调用 call by need 的方式翻译为惰性更好一些),只有在检索时才被赋值( evaluated ),因此在列表比较长的状况下使用内存上更有效。
②以函数形式实现生成器:
其实生成器也是一种迭代器,可是你只能对其迭代一次。这是由于它们并无把全部的值存在内存中,而是在运行时生成值。你经过遍从来使用它们,要么用一个“for”循环,要么将它们传递给任意能够进行迭代的函数和结构。并且实际运用中,大多数的生成器都是经过函数来实现的。
生成器和函数的不一样:
函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。而变成 generator 的函数,在每次调用 next() 的时候执行,遇到 yield语句返回,再次执行时从上次返回的 yield 语句处继续执行。
举个例子:
def odd():
print ( 'step 1' )
yield ( 1 )
print ( 'step 2' )
yield ( 3 )
print ( 'step 3' )
yield ( 5 )
o = odd()
print( next( o ) )
print( next( o ) )
print( next( o ) )
复制代码
输出结果:
step 1
1
step 2
3
step 3
5
复制代码
能够看到,odd 不是普通函数,而是 generator,在执行过程当中,遇到 yield 就中断,下次又继续执行。执行 3 次 yield 后,已经没有 yield 能够执行了,若是你继续打印 print( next( o ) ) ,就会报错的。因此一般在 generator 函数中都要对错误进行捕获。
打印杨辉三角:
def triangles( n ): # 杨辉三角形
L = [1]
while True:
yield L
L.append(0)
L = [ L [ i -1 ] + L [ i ] for i in range (len(L))]
n= 0
for t in triangles( 10 ): # 直接修改函数名便可运行
print(t)
n = n + 1
if n == 10:
break
复制代码
输出结果:
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
复制代码
①反向迭代
使用 Python 中有内置的函数 reversed()
。
要注意一点就是:反向迭代仅仅当对象的大小可预先肯定或者对象实现了 __reversed__()
的特殊方法时才能生效。 若是二者都不符合,那你必须先将对象转换为一个列表才行。
②同时迭代多个序列
为了同时迭代多个序列,使用 zip() 函数,具体示例:
names = ['jaychou', 'zjl', '周杰伦']
ages = [18, 19, 20]
for name, age in zip(names, ages):
print(name,age)
复制代码
输出的结果:
jaychou 18
zjl 19
周杰伦 20
复制代码
其实 zip(a, b)
会生成一个可返回元组 (x, y) 的迭代器,其中 x 来自 a,y 来自 b。 一旦其中某个序列到底结尾,迭代宣告结束。 所以迭代长度跟参数中最短序列长度一致。注意理解这句话,也就是说若是 a , b 的长度不一致的话,以最短的为标准,遍历完后就结束。
在 Python 中,一个 .py
文件就称之为一个模块(Module)。
咱们学习过函数,知道函数是实现一项或多项功能的一段程序 。其实模块就是函数功能的扩展。为何这么说呢?那是由于模块其实就是实现一项或多项功能的程序块。
经过上面的定义,不难发现,函数和模块都是用来实现功能的,只是模块的范围比函数广,在模块中,能够有多个函数。
模块的好处:
模块使用的最大好处是大大提升了代码的可维护性,固然,还提升了代码的复用性。
使用模块还能够避免函数名和变量名冲突,相同名字的变量彻底能够分别存在不一样的模块中。
PS:可是也要注意,变量的名字尽可能不要与内置函数名字冲突。常见的内置函数:连接直达
再这也顺带先延伸下关于包的内容吧:
当编写的模块多了,模块的名字重复的几率就增长了。如何解决这个问题呢?
Python 引入了按目录来组织模块,称为包(Package),好比:
extensions ├─ __init__.py ├─ dog.py └─ cat.py 复制代码
如今
dog.py
模块的名字就变成了extensions.dog
。PS:请注意,每个 package 目录下面都会有一个
__init__.py
的文件,这个文件是必须有的,不然, Python 就把这个目录当成普通目录,而不是一个 package directory。另外如何使用包中的模块(Module)呢?以下编写一个
dog.py
模块:#!/usr/bin/env python3 # -*- coding: utf-8 -*- ' a test module ' __author__ = 'jack guo' import sys def shout(): args = sys.argv if len(args)==1: print('Hello, I'm afei, welcome to world!') elif len(args)==2: print('Hello, %s!' % args[1]) else: print('Yes,sir') if __name__=='__main__': shout() 复制代码
解释下:
第1行注释可让dog.py文件直接在linux上运行; 第2行注释表示.py文件自己使用标准UTF-8编码; 第4行表示模块的文档注释; 第6行表示模块的做者; 注意最后两行代码,当咱们调试dog.py时,shout()会调用,当在其余模块导入dog.py时,shout()不执行。 复制代码
模块的一种标准模板:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- ' a test module ' __author__ = 'jack guo' 复制代码
以上是模块的标准模板,固然,你也能够不这样作。
导入模块咱们使用关键字 import
,语法格式以下:import module1[, module2[,... moduleN]
如:import math
导入标准模块中的 math 模块。
一个模块只会被导入一次,无论你执行了多少次 import
。这样能够防止导入模块被一遍又一遍地执行。
Python 解释器是怎样找到对应的文件的呢?
搜索路径:由一系列目录名组成的。Python 解释器就依次从这些目录中去寻找所引入的模块。这看起来很像环境变量,事实上,也能够经过定义环境变量的方式来肯定搜索路径。搜索路径是在 Python 编译或安装的时候肯定的,安装新的库应该也会修改。搜索路径被存储在 sys 模块中的 path 变量 。能够打印出来:
import sys
print(sys.path)
复制代码
①导入模块的方法
import 模块名
import 模块名 as 新名字
from 模块名 import 函数名
:大型项目中应尽可能避免使用此方法,除非你很是肯定不会形成命名冲突;它有一个好处就是可直接使用function()
而不用加module.function()
了。PS1:导入模块并不意味着在导入时执行某些操做,它们主要用于定义,好比变量、函数和类等。
PS2:可使用
from ··· import *
语句把某个模块中的全部方法属性都导入。
②模块中变量、函数以及类的属性和方法的调用
module.variable
module.function()
module.class.variable
(1)程序所在目录
(2)标准库的安装路径
(3)操做系统环境变量 PYTHONPATH 指向的路径
得到当前 Python 搜索路径的方法:
import sys
print(sys.path)
复制代码
输出:
['D:\\workspace_pycharm', 'D:\\workspace_pycharm', 'D:\\python-practice', 'D:\\devInstall\\devPython\\Python36\\python36.zip', 'D:\\devInstall\\devPython\\Python36\\DLLs', 'D:\\devInstall\\devPython\\Python36\\lib', 'D:\\devInstall\\devPython\\Python36', 'D:\\devInstall\\devPython\\Python36\\lib\\site-packages']
复制代码
sys 模块的 argv 变量的用法:
argv
(argument values) 变量,用 list 存储了命令行的全部参数。argv
至少有一个元素,由于第一个元素永远都是.py
文件的名称。$ python solve.py 0 # 命令行语句
# 得到argv变量的值
sys.argv = ['solve.py', '0']
sys.argv[0] = 'solve.py'
sys.argv[1] = '0'
复制代码
在 Python 函数中,若是一个函数调用了其余函数完成一项功能,咱们称这个函数为主函数,若是一个函数没有调用其余函数,咱们称这种函数为非主函数。主模块和非主模块的定义也相似,若是一个模块被直接使用,而没有被别人调用,咱们称这个模块为主模块,若是一个模块被别人调用,咱们称这个模块为非主模块。
怎么区分主模块和非主模块呢?
能够利用 __name__
属性。若是一个属性的值是 __main__
,那么就说明这个模块是主模块,反之亦然。可是要注意了:这个 __main__
属性只是帮助咱们判断是不是主模块,并非说这个属性决定他们是不是主模块,决定是不是主模块的条件只是这个模块有没有被人调用。以下:
if __name__ == '__main__':
print('main')
else:
print('not main')
复制代码
若是输出结果为 main 则该模块为主模块。
!!!补充: 在初学 Python 过程当中,总能遇到 if __name__ == 'main'
语句,咱们正好来好好了解下。
先举例子,假如 A.py 文件内容以下:
def sayhello():
print('Hello!')
print('Hi!')
print(__name__)
复制代码
输出结果:
Hi!
__main__
复制代码
结果很简单,说明在运行 A.py 自己文件时,变量__name__
的值是__main__
。
现有个 B.py 文件,代码以下:
import A
A.sayhello()
print('End')
复制代码
能够看到,在 B.py 文件中,模块 A 被导入,运行结果以下:
Hi!
A
Hello!
End
复制代码
这里涉及一些语句运行顺序问题,在 B.py 文件中,模块 A 中的 sayhello 函数是调用时才执行的,可是 A 中的 print 语句会马上执行(由于没有缩进,所以与def是平行级别的)。所以会先依次执行:
print('Hi!')
print(__name__)
复制代码
而后执行:
A.sayhello()
print('End')
复制代码
运行结果中Hi!
对应于 A 模块中的 print('Hi!')
,而结果 A 对应于 print(__name__)
,可见当在 B 文件中调用 A 模块时,变量__name__
的值由__main__
变为了模块 A 的名字。
这样的好处是咱们能够在 A.py 文件中进行一些测试,而避免在模块调用的时候产生干扰,好比将 A 文件改成:
def sayhello():
print('Hello!')
print('Hi!')
print(__name__)
if __name__ == '__main__':
print('I am module A')
复制代码
再次单独运行 A.py 文件时,结果中会多出I am module A
语句:
Hi!
__main__
I am module A
复制代码
而运行 B.py 文件,即调用 A 模块时,却不会显示该语句:
Hi!
A
Hello!
End
复制代码
简短总结下:
模块属性
__name__
,它的值由 Python 解释器设定。若是 Python 程序是做为主程序调用,其值就设为__main__
,若是是做为模块被其余文件导入,它的值就是其文件名。每一个模块都有本身的私有符号表,全部定义在模块里面的函数把它当作全局符号表使用。
咱们本身在编写模块时,没必要考虑名字会与其余模块冲突。可是也要注意,尽可能不要与内置函数名字冲突。可是这里也有个问题,若是不一样的人编写的模块名相同怎么办?为了不模块名冲突,Python 又引入了按目录来组织模块的方法,称为包(Package)。
仔细观察的人,基本会发现,每个包目录下面都会有一个 __init__.py
的文件。这个文件是必须的,不然,Python 就把这个目录当成普通目录,而不是一个包。 __init__.py
能够是空文件,也能够有 Python 代码,由于 __init__.py
自己就是一个模块,而它对应的模块名就是它的包名。
__init__.py
的模块文件,内容能够为空(普通文件夹和包的区别)。①包的存放
site-packages
文件夹里,由于它就是用来存放你的模块文件的。sys.path.append(‘模块的存放位置’)
只是在运行时生效,运行结束后失效。②包中模块的导入
import 包名.模块名
import 包名.模块名 as 新名字
from 包名 import 模块名
③包中模块的变量、函数以及类的属性和方法的调用
package.module.variable
package.module.function()
package.module.class.variable
学习过 Java 的同窗都知道,Java 的类里面能够给方法和属性定义公共的( public )或者是私有的 ( private ),这样作主要是为了咱们但愿有些函数和属性能给别人使用或者只能内部使用。 经过学习 Python 中的模块,其实和 Java 中的类类似,那么咱们怎么实如今一个模块中,有的函数和变量给别人使用,有的函数和变量仅仅在模块内部使用呢?
在 Python 中,是经过 _
前缀来实现的。正常的函数和变量名是公开的(public),能够被直接引用,好比:abc
,ni12
,PI
等。
相似__xxx__
这样的变量是特殊变量,能够被直接引用,可是有特殊用途,好比上面的 __name__
就是特殊变量,还有 __author__
也是特殊变量,用来标明做者。注意,咱们本身的变量通常不要用这种变量名;相似_xxx
和 __xxx
这样的函数或变量就是非公开的(private),不该该被直接引用,好比 _abc
,__abc
等.
注意:这里是说不该该,而不是不能。由于 Python 种并无一种方法能够彻底限制访问 private 函数或变量,可是,从编程习惯上不该该引用 private 函数或变量。
Python 对属性的访问控制是靠程序员自觉的。
咱们也能够把方法当作是类的属性的,那么方法的访问控制也是跟属性是同样的,也是没有实质上的私有方法。一切都是靠程序员自觉遵照 Python 的编程规范。
@classmethod
:调用的时候直接使用类名类调用,而不是某个对象
@property
:能够像访问属性同样调用方法
class UserInfo:
...
@classmethod
def get_name(cls):
return cls.lv
@property
def get_age(self):
return self._age
if __name__ == '__main__':
...
# 直接使用类名类调用,而不是某个对象
print(UserInfo.lv)
# 像访问属性同样调用方法(注意看get_age是没有括号的)
print(userInfo.get_age)
复制代码
语法格式:
class ClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
复制代码
固然上面的是单继承,Python 也是支持多继承的(注意: Java 是单继承、多实现),具体的语法以下:
class ClassName(Base1,Base2,Base3):
<statement-1>
.
.
.
<statement-N>
复制代码
多继承有一点须要注意的:如果父类中有相同的方法名,而在子类使用时未指定,Python 在圆括号中父类的顺序,从左至右搜索 , 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
继承的子类的好处:
看个例子就行了:
class User(object):
def __init__(self, name):
self.name = name
def printUser(self):
print('Hello !' + self.name)
class UserVip(User):
def printUser(self):
print('Hello ! 尊敬的Vip用户:' + self.name)
class UserGeneral(User):
def printUser(self):
print('Hello ! 尊敬的用户:' + self.name)
def printUserInfo(user):
user.printUser()
if __name__ == '__main__':
userVip = UserVip('大金主')
printUserInfo(userVip)
userGeneral = UserGeneral('水货')
printUserInfo(userGeneral)
复制代码
输出结果:
Hello ! 尊敬的Vip用户:大金主
Hello ! 尊敬的用户:水货
复制代码
能够看到,userVip 和 userGeneral 是两个不一样的对象,对它们调用 printUserInfo 方法,它们会自动调用实际类型的 printUser 方法,做出不一样的响应。这就是多态的魅力。
PS:有了继承,才有了多态,也会有不一样类的对象对同一消息会做出不一样的相应。
在 Python 中,全部以 "**" 双下划线包起来的方法,都统称为"魔术方法"。好比咱们接触最多的 init__
。魔术方法有什么做用呢?
使用这些魔术方法,咱们能够构造出优美的代码,将复杂的逻辑封装成简单的方法。
咱们可使用 Python 内置的方法 dir()
来列出类中全部的魔术方法。示例以下:
class User(object):
pass
if __name__ == '__main__':
print(dir(User()))
复制代码
输出的结果:
能够看到,一个类的魔术方法仍是挺多的,截图没有截全。不过咱们只须要了解一些常见和经常使用的魔术方法就行了。
一、属性的访问控制
Python 没有真正意义上的私有属性。而后这就致使了对 Python 类的封装性比较差。咱们有时候会但愿 Python 可以定义私有属性,而后提供公共可访问的 get 方法和 set 方法。Python 其实能够经过魔术方法来实现封装。
方法 | 说明 |
---|---|
__getattr__(self, name) |
该方法定义了你试图访问一个不存在的属性时的行为。所以,重载该方法能够实现捕获错误拼写而后进行重定向,或者对一些废弃的属性进行警告。 |
__setattr__(self, name, value) |
定义了对属性进行赋值和修改操做时的行为。无论对象的某个属性是否存在,都容许为该属性进行赋值。有一点须要注意,实现 __setattr__ 时要避免"无限递归"的错误 |
__delattr__(self, name) |
__delattr__ 与 __setattr__ 很像,只是它定义的是你删除属性时的行为。实现 __delattr__ 是同时要避免"无限递归"的错误 |
__getattribute__(self, name) |
__getattribute__ 定义了你的属性被访问时的行为,相比较,__getattr__ 只有该属性不存在时才会起做用。所以,在支持 __getattribute__ 的 Python 版本,调用__getattr__ 前一定会调用 __getattribute__ ,__getattribute__ 一样要避免"无限递归"的错误。 |
二、对象的描述器
通常来讲,一个描述器是一个有“绑定行为”的对象属性 (object attribute),它的访问控制被描述器协议方法重写。这些方法是 __get__()
,__set__()
和 __delete__()
。有这些方法的对象叫作描述器。
默认对属性的访问控制是从对象的字典里面 (__dict__)
中获取 (get) , 设置 (set) 和删除 (delete) 。举例来讲, a.x
的查找顺序是 a.__dict__['x']
,而后 type(a).__dict__['x']
,而后找 type(a)
的父类 ( 不包括元类 (metaclass) )。若是查找到的值是一个描述器,Python 就会调用描述器的方法来重写默认的控制行为。这个重写发生在这个查找环节的哪里取决于定义了哪一个描述器方法。注意,只有在新式类中时描述器才会起做用。
至于新式类最大的特色就是全部类都继承自 type 或者 object 的类。
在面向对象编程时,若是一个类的属性有相互依赖的关系时,使用描述器来编写代码能够很巧妙的组织逻辑。在 Django 的 ORM 中,models.Model
中的 InterField 等字段,就是经过描述器来实现功能的。
看一个例子:
class User(object):
def __init__(self, name='小明', sex='男'):
self.sex = sex
self.name = name
def __get__(self, obj, objtype):
print('获取 name 值')
return self.name
def __set__(self, obj, val):
print('设置 name 值')
self.name = val
class MyClass(object):
x = User('小明', '男')
y = 5
if __name__ == '__main__':
m = MyClass()
print(m.x)
print('\n')
m.x = '大明'
print(m.x)
print('\n')
print(m.x)
print('\n')
print(m.y)
复制代码
输出结果:
获取 name 值
小明
设置 name 值
获取 name 值
大明
获取 name 值
大明
5
复制代码
三、自定义容器(Container)
咱们知道在 Python 中,常见的容器类型有:dict、tuple、list、string。其中也提到过可容器和不可变容器的概念。其中 tuple、string 是不可变容器,dict、list 是可变容器。
可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。
那么这里先提出一个问题,这些数据结构就够咱们开发使用吗?不够的时候,或者说有些特殊的需求不能单单只使用这些基本的容器解决的时候,该怎么办呢?
这个时候就须要自定义容器了,那么具体咱们该怎么作呢?
功能 | 说明 |
---|---|
自定义不可变容器类型 | 须要定义 __len__ 和 __getitem__ 方法 |
自定义可变类型容器 | 在不可变容器类型的基础上增长定义 __setitem__ 和 __delitem__ |
自定义的数据类型须要迭代 | 需定义 __iter__ |
返回自定义容器的长度 | 需实现 __len__(self) |
自定义容器能够调用 self[key] ,若是 key 类型错误,抛出 TypeError,若是无法返回 key对应的数值时,该方法应该抛出 ValueError |
须要实现 __getitem__(self, key) |
当执行 self[key] = value 时 |
调用是 __setitem__(self, key, value) 这个方法 |
当执行 del self[key] 方法 |
其实调用的方法是 __delitem__(self, key) |
当你想你的容器能够执行 for x in container: 或者使用 iter(container) 时 |
须要实现 __iter__(self) ,该方法返回的是一个迭代器 |
还有不少魔术方法,好比运算符相关的模式方法,就不在该文展开了。
举例,直接看代码:
from enum import Enum
Month = Enum('Month1', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# 遍历枚举类型
for name, member in Month.__members__.items():
print(name, '---------', member, '----------', member.value)
# 直接引用一个常量
print('\n', Month.Jan)
复制代码
输出结果:
Jan --------- Month1.Jan ---------- 1
Feb --------- Month1.Feb ---------- 2
Mar --------- Month1.Mar ---------- 3
Apr --------- Month1.Apr ---------- 4
May --------- Month1.May ---------- 5
Jun --------- Month1.Jun ---------- 6
Jul --------- Month1.Jul ---------- 7
Aug --------- Month1.Aug ---------- 8
Sep --------- Month1.Sep ---------- 9
Oct --------- Month1.Oct ---------- 10
Nov --------- Month1.Nov ---------- 11
Dec --------- Month1.Dec ---------- 12
Month.Jan
复制代码
可见,咱们能够直接使用 Enum 来定义一个枚举类。上面的代码,咱们建立了一个有关月份的枚举类型 Month,这里要注意的是构造参数,第一个参数 Month 表示的是该枚举类的类名,第二个 tuple 参数,表示的是枚举类的值; 固然,枚举类经过 __members__
遍历它的全部成员的方法。
注意的一点是 , member.value 是自动赋给成员的 int 类型的常量,默认是从 1 开始的。并且 Enum 的成员均为单例(Singleton),而且不可实例化,不可更改。
有时候咱们须要控制枚举的类型,那么咱们能够 Enum 派生出自定义类来知足这种须要。修改上面的例子:
from enum import Enum, unique
Enum('Month1', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# @unique 装饰器能够帮助咱们检查保证没有重复值
@unique
class Month1(Enum):
Jan = 'January'
Feb = 'February'
Mar = 'March'
Apr = 'April'
May = 'May'
Jun = 'June'
Jul = 'July'
Aug = 'August'
Sep = 'September '
Oct = 'October'
Nov = 'November'
Dec = 'December'
if __name__ == '__main__':
print(Month1.Jan, '----------',
Month1.Jan.name, '----------', Month1.Jan.value)
for name, member in Month1.__members__.items():
print(name, '----------', member, '----------', member.value)
复制代码
输出结果:
Month1.Jan ---------- Jan ---------- January
Jan ---------- Month1.Jan ---------- January
Feb ---------- Month1.Feb ---------- February
Mar ---------- Month1.Mar ---------- March
Apr ---------- Month1.Apr ---------- April
May ---------- Month1.May ---------- May
Jun ---------- Month1.Jun ---------- June
Jul ---------- Month1.Jul ---------- July
Aug ---------- Month1.Aug ---------- August
Sep ---------- Month1.Sep ---------- September
Oct ---------- Month1.Oct ---------- October
Nov ---------- Month1.Nov ---------- November
Dec ---------- Month1.Dec ---------- December
复制代码
由于枚举成员不是有序的,因此它们只支持经过标识(identity) 和相等性 (equality) 进行比较。下面来看看 ==
和 is
的使用:
from enum import Enum
class User(Enum):
Twowater = 98
Liangdianshui = 30
Tom = 12
Twowater = User.Twowater
Liangdianshui = User.Liangdianshui
print(Twowater == Liangdianshui, Twowater == User.Twowater)
print(Twowater is Liangdianshui, Twowater is User.Twowater)
try:
print('\n'.join(' ' + s.name for s in sorted(User)))
except TypeError as err:
print(' Error : {}'.format(err))
复制代码
输出结果:
False True
False True
Error : '<' not supported between instances of 'User' and 'User' 复制代码
能够看看最后的输出结果,报了个异常,那是由于大于和小于比较运算符引起 TypeError 异常。也就是 Enum 类的枚举是不支持大小运算符的比较的。
可是使用 IntEnum 类进行枚举,就支持比较功能。
import enum
class User(enum.IntEnum):
Twowater = 98
Liangdianshui = 30
Tom = 12
try:
print('\n'.join(s.name for s in sorted(User)))
except TypeError as err:
print(' Error : {}'.format(err))
复制代码
输出结果:
Tom
Liangdianshui
Twowater
复制代码
经过输出的结果能够看到,枚举类的成员经过其值得大小进行了排序。也就是说能够进行大小的比较。
在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在 Python 中这一点也是同样的。可是,Python 中的类有一点跟大多数的编程语言不一样,在 Python 中,能够把类理解成也是一种对象。对的,这里没有写错,就是对象。
由于只要使用关键字 class
,Python 解释器在执行的时候就会建立一个对象。如:
class ObjectCreator(object):
pass
复制代码
当程序运行这段代码的时候,就会在内存中建立一个对象,名字就是ObjectCreator。这个对象(类)自身拥有建立对象(类实例)的能力,而这就是为何它是一个类的缘由。
由于类也是对象,因此咱们能够在程序运行的时候建立类。Python 是动态语言。动态语言和静态语言最大的不一样,就是函数和类的定义,不是编译时定义的,而是运行时动态建立的。在以前,咱们先了了解下 type()
函数。
class Hello(object):
def hello(self, name='Py'):
print('Hello,', name)
复制代码
而后再另一个模块引用 hello 模块,输出相应信息。(其中 type()
函数的做用是能够查看一个类型和变量的类型。)
from com.strivebo.hello import Hello
h = Hello()
h.hello()
print(type(Hello))
print(type(h))
复制代码
输出信息:
Hello, Py
<class 'type'>
<class 'com.twowater.hello.Hello'>
复制代码
上面也提到过,type()
函数能够查看一个类型或变量的类型,Hello 是一个 class ,它的类型就是 type ,而 h 是一个实例,它的类型就是 com.strivebo.hello.Hello
。前面的 com.strivebo
是个人包名,hello 模块在该包名下。
在这里还要细想一下,上面的例子中,咱们使用 type()
函数查看一个类型或者变量的类型。其中查看了一个 Hello class 的类型,打印的结果是: <class 'type'>
。其实 type()
函数不只能够返回一个对象的类型,也能够建立出新的类型。class 的定义是运行时动态建立的,而建立 class 的方法就是使用 type()
函数。好比咱们能够经过 type()
函数建立出上面例子中的 Hello 类,具体看下面的代码:
def printHello(self, name='Py'):
# 定义一个打印 Hello 的函数
print('Hello,', name)
# 建立一个 Hello 类
Hello = type('Hello', (object,), dict(hello=printHello))
# 实例化 Hello 类
h = Hello()
# 调用 Hello 类的方法
h.hello()
# 查看 Hello class 的类型
print(type(Hello))
# 查看实例 h 的类型
print(type(h))
复制代码
输出结果:
Hello, Py
<class 'type'>
<class '__main__.Hello'>
复制代码
在这里,需先了解下经过 type()
函数建立 class 对象的参数说明:
Hello
(object,)
printHello
绑定在方法名 hello
中具体的模式以下:type(类名, 父类的元组(针对继承的状况,能够为空),包含属性的字典(名称和值))
好了,了解完具体的参数使用以外,咱们看看输出的结果,能够看到,经过 type()
函数建立的类和直接写 class 是彻底同样的,由于 Python 解释器遇到 class 定义时,仅仅是扫描一下 class 定义的语法,而后调用 type()
函数建立出 class 的。
咱们建立类的时候,大多数是为了建立类的实例对象。那么元类呢?元类就是用来建立类的。也能够换个理解方式就是:元类就是类的类。
经过上面 type()
函数的介绍,咱们知道能够经过 type()
函数建立类:MyClass = type('MyClass', (), {})
实际上 type() 函数是一个元类。type()
就是 Python 在背后用来建立全部类的元类。
那么如今咱们也能够猜到一下为何 type()
函数是 type 而不是 Type呢?
这多是为了和 str 保持一致性,str 是用来建立字符串对象的类,而 int 是用来建立整数对象的类。type 就是建立类对象的类。
能够看到,上面的全部东西,也就是全部对象都是经过类来建立的,那么咱们可能会好奇,__class__
的 __class__
会是什么呢?换个说法就是,建立这些类的类是什么呢?
print(age.__class__.__class__)
print(name.__class__.__class__)
print(fu.__class__.__class__)
print(mEat.__class__.__class__)
复制代码
输出结果:
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
复制代码
能够看出,把他们类的类打印结果。发现打印出来的 class 都是 type 。
一开始也提到了,元类就是类的类。也就是元类就是负责建立类的一种东西。你也能够理解为,元类就是负责生成类的。而 type 就是内建的元类。也就是 Python 自带的元类。
链接起来就是:先定义 metaclass,就能够建立类,最后建立实例。
因此,metaclass 容许你建立类或者修改类。换句话说,你能够把类当作是 metaclass 建立出来的“实例”。
class MyObject(object):
__metaclass__ = something…
[…]
复制代码
若是是这样写的话,Python 就会用元类来建立类 MyObject。当你写下 class MyObject(object)
,可是类对象 MyObject 尚未在内存中建立。Python 会在类的定义中寻找 __metaclass__
属性,若是找到了,Python 就会用它来建立类 MyObject,若是没有找到,就会用内建的 type 函数来建立这个类。若是还不怎么理解,看下下面的流程图:
举个实例:
class Foo(Bar):
pass
复制代码
它的流程是怎样的呢?
首先判断 Foo 中是否有
__metaclass__
这个属性?若是有,Python 会在内存中经过__metaclass__
建立一个名字为 Foo 的类对象(注意,这里是类对象)。若是 Python 没有找到__metaclass__
,它会继续在 Bar(父类)中寻找__metaclass__
属性,并尝试作和前面一样的操做。若是 Python在任何父类中都找不到__metaclass__
,它就会在模块层次中去寻找__metaclass__
,并尝试作一样的操做。若是仍是找不到__metaclass__
,Python 就会用内置的 type 来建立这个类对象。
其实 __metaclass__
就是定义了 class 的行为。相似于 class 定义了 instance 的行为,metaclass 则定义了 class 的行为。能够说,class 是 metaclass 的 instance。
如今,咱们基本了解了 __metaclass__
属性,可是,也没讲过如何使用这个属性,或者说这个属性能够放些什么?
答案就是:能够建立一个类的东西。那么什么能够用来建立一个类呢?type,或者任何使用到 type 或者子类化 type 的东东均可以。
元类的主要目的就是为了当建立类时可以自动地改变类。一般,你会为 API 作这样的事情,你但愿能够建立符合当前上下文的类。
假想一个很傻的例子,你决定在你的模块里全部的类的属性都应该是大写形式。有好几种方法能够办到,但其中一种就是经过在模块级别设定__metaclass__
。采用这种方法,这个模块中的全部类都会经过这个元类来建立,咱们只须要告诉元类把全部的属性都改为大写形式就万事大吉了。
总结:Python 中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了 type。type 其实是它本身的元类,在纯 Python 环境中这可不是你可以作到的,这是经过在实现层面耍一些小手段作到的。
线程和进程的概念我就很少赘述了。可自行网上搜索查找资料了解下。
直接看问题:在 Python 中咱们要同时执行多个任务怎么办?
有两种解决方案:
固然还有第三种方法,就是启动多个进程,每一个进程再启动多个线程,这样同时执行的任务就更多了,固然这种模型更复杂,实际不多采用。
总结一下就是,多任务的实现有3种方式:
同时执行多个任务一般各个任务之间并非没有关联的,而是须要相互通讯和协调,有时,任务 1 必须暂停等待任务 2 完成后才能继续执行,有时,任务 3 和任务 4 又不能同时执行,因此,多进程和多线程的程序的复杂度要远远高于咱们前面写的单进程单线程的程序。
其实建立线程以后,线程并非始终保持一个状态的,其状态大概以下:
线程有着不一样的状态,也有不一样的类型。大体可分为:
线程的建立:
Python 提供两个模块进行多线程的操做,分别是 thread
和 threading
前者是比较低级的模块,用于更底层的操做,通常应用级别的开发不经常使用。
import time
import threading
class MyThread(threading.Thread):
def run(self):
for i in range(5):
print('thread {}, @number: {}'.format(self.name, i))
time.sleep(1)
def main():
print("Start main threading")
# 建立三个线程
threads = [MyThread() for i in range(3)]
# 启动三个线程
for t in threads:
t.start()
print("End Main threading")
if __name__ == '__main__':
main()
复制代码
这块的内容还有不少,因为该文重点仍是为讲解 Python 的基础知识。线程和进程的内容更多仍是到网上搜索资料学习,亦或是往后有时间我再更新于此。
正则表达式是一个特殊的字符序列,用于判断一个字符串是否与咱们所设定的字符序列是否匹配,也就是说检查一个字符串是否与某种模式匹配。
Python 自 1.5 版本起增长了 re 模块,它提供 Perl 风格的正则表达式模式。re 模块使 Python 语言拥有所有的正则表达式功能。
以下代码:
# 设定一个常量
a = '学习Python不难'
# 判断是否有 “Python” 这个字符串,使用 PY 自带函数
print('是否含有“Python”这个字符串:{0}'.format(a.index('Python') > -1))
print('是否含有“Python”这个字符串:{0}'.format('Python' in a))
复制代码
输出结果:
是否含有“Python”这个字符串:True
是否含有“Python”这个字符串:True
复制代码
上面用 Python 自带函数就能解决的问题,咱们就不必使用正则表达式了,这样作画蛇添足。
直接举个 Python 中正则表达式使用例子好了:找出字符串中的全部小写字母。
首先咱们在 findall 函数中第一个参数写正则表达式的规则,其中[a-z]
就是匹配任何小写字母,第二个参数只要填写要匹配的字符串就好了。具体以下:
import re
# 设定一个常量
a = '学习Python不难'
# 选择 a 里面的全部小写英文字母
re_findall = re.findall('[a-z]', a)
print(re_findall)
复制代码
输出结果:
['y', 't', 'h', 'o', 'n']
复制代码
这样咱们就拿到了字符串中的全部小写字母了。
补充:
- 贪婪模式:它的特性是一次性地读入整个字符串,若是不匹配就吐掉最右边的一个字符再匹配,直到找到匹配的字符串或字符串的长度为 0 为止。它的宗旨是读尽量多的字符,因此当读到第一个匹配时就马上返回。
- 懒惰模式:它的特性是从字符串的左边开始,试图不读入字符串中的字符进行匹配,失败,则多读一个字符,再匹配,如此循环,当找到一个匹配时会返回该匹配的字符串,而后再次进行匹配直到字符串结束。
关于正则表达式的更多的学习仍是找网上资料看看吧。
经过解决一个需求问题来了解闭包。
这个需求是这样的,咱们须要一直记录本身的学习时间,以分钟为单位。就比如我学习了 2 分钟,就返回 2 ,而后隔了一阵子,我学习了 10 分钟,那么就返回 12 ,像这样把学习时间一直累加下去。
面对这个需求,咱们通常都会建立一个全局变量来记录时间,而后用一个方法来新增每次的学习时间,一般都会写成下面这个形式:
time = 0
def insert_time(min):
time = time + min
return time
print(insert_time(2))
print(insert_time(10))
复制代码
其实,这个在 Python 里面是会报错的。会报以下错误:UnboundLocalError: local variable 'time' referenced before assignment
那是由于,在 Python 中,若是一个函数使用了和全局变量相同的名字且改变了该变量的值,那么该变量就会变成局部变量,那么就会形成在函数中咱们没有进行定义就引用了,因此会报该错误。
咱们可使用 global 关键字,具体修改以下:
time = 0
def insert_time(min):
global time
time = time + min
return time
print(insert_time(2))
print(insert_time(10))
复制代码
输出结果以下:
2
12
复制代码
但是啊,这里使用了全局变量,咱们在开发中能尽可能避免使用全局变量的就尽可能避免使用。由于不一样模块,不一样函数均可以自由的访问全局变量,可能会形成全局变量的不可预知性。好比程序员甲修改了全局变量 time 的值,而后程序员乙同时也对 time 进行了修改,若是其中有错误,这种错误是很难发现和更正的。
这时候咱们使用闭包来解决一下,先直接看代码:
time = 0
def study_time(time):
def insert_time(min):
nonlocal time
time = time + min
return time
return insert_time
f = study_time(time)
print(f(2))
print(time)
print(f(10))
print(time)
复制代码
输出结果以下:
2
0
12
0
复制代码
这里最直接的表现就是全局变量 time 至此至终都没有修改过,这里仍是用了 nonlocal 关键字,表示在函数或其余做用域中使用外层(非全局)变量。那么上面那段代码具体的运行流程是怎样的。咱们能够看下下图:
这种内部函数的局部做用域中能够访问外部函数局部做用域中变量的行为,咱们称为: 闭包。 更加直接的表达方式就是,当某个函数被当成对象返回时,夹带了外部变量,就造成了一个闭包。
有没有什么办法来验证一下这个函数就是闭包呢?
有的,全部函数都有一个 __closure__
属性,若是函数是闭包的话,那么它返回的是一个由 cell 组成的元组对象。cell 对象的 cell_contents 属性就是存储在闭包中的变量。看代码:
ime = 0
def study_time(time):
def insert_time(min):
nonlocal time
time = time + min
return time
return insert_time
f = study_time(time)
print(f.__closure__)
print(f(2))
print(time)
print(f.__closure__[0].cell_contents)
print(f(10))
print(time)
print(f.__closure__[0].cell_contents)
复制代码
打印结果为:
(<cell at 0x0000000000410C48: int object at 0x000000001D6AB420>,)
2
0
2
12
0
12
复制代码
从打印结果可见,传进来的值一直存储在闭包的 cell_contents 中,所以,这也就是闭包的最大特色,能够将父函数的变量与其内部定义的函数绑定。就算生成闭包的父函数已经释放了,闭包仍然存在。
闭包的过程其实比如类(父函数)生成实例(闭包),不一样的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时建立,通常程序执行完毕后做用域才释放,所以对一些须要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活。
经过一个需求,一步一步来了解 Python 装饰器。首先有这么一个输出员工打卡信息的函数:
def punch():
print('昵称:小明 部门:研发部 上班打卡成功')
punch()
复制代码
输出的结果:
昵称:小明 部门:研发部 上班打卡成功
复制代码
而后,产品反馈,不行啊,怎么上班打卡没有具体的日期,加上打卡的具体日期吧,这应该很简单,分分钟解决啦。好吧,那就直接添加打印日期的代码吧,以下:
import time
def punch():
print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
print('昵称:小明 部门:研发部 上班打卡成功')
punch()
复制代码
输出的结果:
2018-01-09
昵称:小明 部门:研发部 上班打卡成功
复制代码
这样改是能够,但是这样改是改变了函数的功能结构的,自己这个函数定义的时候就是打印某个员工的信息和提示打卡成功,如今增长打印日期的代码,可能会形成不少代码重复的问题。好比,还有一个地方只须要打印员工信息和打卡成功就好了,不须要日期,那么你又要重写一个函数吗?并且打印当前日期的这个功能方法是常用的,是能够做为公共函数给各个模块方法调用的。固然,这都是做为一个总体项目来考虑的。
既然是这样,咱们可使用函数式编程来修改这部分的代码。由于经过以前的学习,咱们知道 Python 函数有两个特色,函数也是一个对象,并且函数里能够嵌套函数,那么修改一下代码变成下面这个样子:
import time
def punch():
print('昵称:小明 部门:研发部 上班打卡成功')
def add_time(func):
print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
func()
add_time(punch)
复制代码
输出的结果:
2018-01-09
昵称:小明 部门:研发部 上班打卡成功
复制代码
这样是否是发现,这样子就没有改动 punch
方法,并且任何须要用到打印当前日期的函数均可以把函数传进 add_time 就能够了。
使用函数编程是否是很方便,可是,咱们每次调用的时候,咱们都不得不把原来的函数做为参数传递进去,还能不能有更好的实现方式呢?有的,就是本文要介绍的装饰器,由于装饰器的写法其实跟闭包是差很少的,不过没有了自由变量,那么这里直接给出上面那段代码的装饰器写法,来对比一下,装饰器的写法和函数式编程有啥不一样。
import time
def decorator(func):
def punch():
print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
func()
return punch
def punch():
print('昵称:小明 部门:研发部 上班打卡成功')
f = decorator(punch)
f()
复制代码
输出的结果:
2018-01-09
昵称:小明 部门:研发部 上班打卡成功
复制代码
经过代码,能知道装饰器函数通常作这三件事:
咱们看上面的代码能够知道, Python 在引入装饰器 (Decorator) 的时候,没有引入任何新的语法特性,都是基于函数的语法特性。这也就说明了装饰器不是 Python 特有的,而是每一个语言通用的一种编程思想。只不过 Python 设计出了 @
语法糖,让定义装饰器,把装饰器调用原函数再把结果赋值为原函数的对象名的过程变得更加简单,方便,易操做,因此 Python 装饰器的核心能够说就是它的语法糖。
那么怎么使用它的语法糖呢?很简单,根据上面的写法写完装饰器函数后,直接在原来的函数上加 @
和装饰器的函数名。以下:
import time
def decorator(func):
def punch():
print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
func()
return punch
@decorator
def punch():
print('昵称:小明 部门:研发部 上班打卡成功')
punch()
复制代码
输出结果:
2018-01-09
昵称:小明 部门:研发部 上班打卡成功
复制代码
那么这就很方便了,方便在咱们的调用上,好比例子中的,使用了装饰器后,直接在本来的函数上加上装饰器的语法糖就能够了,本函数也无虚任何改变,调用的地方也不需修改。
不过这里一直有个问题,就是输出打卡信息的是固定的,那么咱们须要经过参数来传递,装饰器该怎么写呢?装饰器中的函数可使用 *args
可变参数,但是仅仅使用 *args
是不能彻底包括全部参数的状况,好比关键字参数就不能了,为了能兼容关键字参数,咱们还须要加上 **kwargs
。
所以,装饰器的最终形式能够写成这样:
import time
def decorator(func):
def punch(*args, **kwargs):
print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
func(*args, **kwargs)
return punch
@decorator
def punch(name, department):
print('昵称:{0} 部门:{1} 上班打卡成功'.format(name, department))
@decorator
def print_args(reason, **kwargs):
print(reason)
print(kwargs)
punch('小明', '研发部')
print_args('小明', sex='男', age=99)
复制代码
输出的结果:
2018-01-09
昵称:小明 部门:研发部 上班打卡成功
2018-01-09
小明
{'sex': '男', 'age': 99}
复制代码
有时候python自带异常不够用,如同java,python也能够自定义异常,而且能够手动抛出。注意,自定义异常只能由本身抛出。python解释器是不知道用户自定义异常是什么鬼的。
raise语句:
主动抛出异常。 格式:
raise 异常名称(‘异常描述’)
raise RuntimeError('testError')
复制代码
主动抛出这个异常,并加以解释。
自定义异常:
python的异常分为两种.
首先看看 python 的异常继承树:
咱们能够看到 python 的异常有个大基类。而后继承的是 Exception。因此咱们自定义类也必须继承 Exception。
#最简单的自定义异常
class FError(Exception):
pass
复制代码
抛出异常、用try-except抛出:
try:
raise FError("自定义异常")
except FError as e:
print(e)
复制代码
在这里给一个简单的自定义异常类模版。
class CustomError(Exception):
def __init__(self,ErrorInfo):
super().__init__(self) #初始化父类
self.errorinfo=ErrorInfo
def __str__(self):
return self.errorinfo
if __name__ == '__main__':
try:
raise CustomError('客户异常')
except CustomError as e:
print(e)
复制代码
——from:blog.csdn.net/skullFang/a…
本文内容大部分来源: