python(二)
模块
什么是模块?
常见的模块场景:一个模块包含了python定义和声明的文件,文件名是模块名加上.py的后缀(module.py).
但其实import加载的模块分为四个通用类别:
- 使用python编写的代码(.py文件)
- 已被编译为共享库或DLL的C或C++扩展
- 包好一组模块的包
- 使用C编写并链接到python解释器的内置模块
模块就是:一个复杂的功能来,可能需要多个函数才能完成(函数又可以在不同的.py文件中),n个 .py 文件组成的代码集合就称为模块。
模块可以分为:自定义模块、内置模块、开源模块。
自定义模块
在pycharm中自定义模块:
1.
模块的导入
想使用模块,就必须导入模块,才能使用,也下是导入模块的方式:
import module #导入一个模块,也可以导入多个模块,也','进行分隔:import module1,module2,...
from module.xx.xx import xx
from module.xx.xx import xx as rename
from module.xx.xx import * #module中所有的不是以下划线(_)开头的名字都导入到当前位置,大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题
#也会将模块中的所有的变量名复制到
测试:
测试结果:
导入模块其实就是告诉Python解释器去解释那个py文件
- 导入一个py文件,解释器解释该py文件
- 导入一个包,解释器解释该包下的 __init__.py 文件
python是在哪里去寻找这些模块的呢,搜索路径是?
是通过sys.path
>>> import sys
>>> sys.path
['', 'G:\\Sofeware\\Anaconda\\python36.zip', 'G:\\Sofeware\\Anaconda\\DLLs', 'G:\\Sofeware\\Anaconda\\lib', 'G:\\Sofeware\\Anaconda', 'G:\\Sofeware\\Anaconda\\lib\\site-packages', 'G:\\Sofeware\\Anaconda\\lib\\site-packages\\win32', 'G:\\Sofeware\\Anaconda\\lib\\site-packages\\win32\\lib', 'G:\\Sofeware\\Anaconda\\lib\\site-packages\\Pythonwin']
如果sys.path路径列表没有你想要的路径,可以通过 sys.path.append('路径') 添加。
通过os模块可以获取各种目录,例如:
import sys
import os
pre_path = os.path.abspath('../')
sys.path.append(pre_path)
包
包是一种通过使用‘.模块名’来组织python模块名称空间的方式。
1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
-
包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
-
import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件
注意:
1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
创建目录
创建目录代码
import os
os.makedirs('glance/api')
os.makedirs('glance/cmd')
os.makedirs('glance/db')
l = []
l.append(open('glance/__init__.py','w'))
l.append(open('glance/api/__init__.py','w'))
l.append(open('glance/api/policy.py','w'))
l.append(open('glance/api/versions.py','w'))
l.append(open('glance/cmd/__init__.py','w'))
l.append(open('glance/cmd/manage.py','w'))
l.append(open('glance/db/models.py','w'))
map(lambda f:f.close() ,l)
目录的结构:
glance/ #Top-level package
├── __init__.py #Initialize the glance package
├── api #Subpackage for api
│ ├── __init__.py
│ ├── policy.py
│ └── versions.py
├── cmd #Subpackage for cmd
│ ├── __init__.py
│ └── manage.py
└── db #Subpackage for db
├── __init__.py
└── models.py
文件内容
#policy.py
def get():
print('from policy.py')
#versions.py
def create_resource(conf):
print('from version.py: ',conf)
#manage.py
def main():
print('from manage.py')
#models.py
def register_models(engine):
print('from models.py: ',engine)
import导入
import...from...导入
__init__.py文件
不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码。
** from glance.api import ***
此处是想从包api中导入所有,实际上该语句只会导入包api下__init__.py文件中定义的名字,我们可以在这个文件中定义__all___:
此时我们在于glance同级的文件中执行from glance.api import *就导入__all__中的内容(versions仍然不能导入)
glance/
├── __init__.py
├── api
│ ├── __init__.py __all__ = ['policy','versions']
│ ├── policy.py
│ └── versions.py
├── cmd __all__ = ['manage']
│ ├── __init__.py
│ └── manage.py
└── db __all__ = ['models']
├── __init__.py
└── models.py
from glance.api import *
policy.get()
绝对导入和相对导入
我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:
绝对导入:以glance作为起始
相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
例如:我们在glance/api/version.py中想要导入glance/cmd/manage.py
在glance/api/version.py
#绝对导入
from glance.cmd import manage
manage.main()
#相对导入
from ..cmd import manage
manage.main(
测试结果:注意一定要在于glance同级的文件中测试
from glance.cmd import manage
manage.main()
#_________________
>>> from glance.cmd import manage
>>> manage.main()
from manage.py
cmd上进行测试
特别需要注意的是:可以用import导入内置或者第三方模块(已经在sys.path中),但是要绝对避免使用import来导入自定义包的子模块(没有在sys.path中),应该使用from... import ...的绝对或者相对导入,且包的相对导入只能用from的形式。
比如我们想在glance/api/versions.py中导入glance/api/policy.py,很有可以是下面的的做法,直接导入:
#在version.py中
import policy
policy.get()
单独运行version.py是没有问题的,运行version.py的路径搜索就是从当前路径开始的,于是在导入policy时能在当前目录下找到
你子包中的模块version.py极有可能是被一个glance包同一级别的其他文件导入,比如我们在于glance同级下的一个test.py文件中导入version.py,如下
from glance.api import versions
'''
#执行结果:
ModuleNotFoundError: No module named 'policy'
#分析:
此时我们导入versions在versions.py中执行
import policy需要找从sys.path也就是从当前目录找policy.py,
这必然是找不到的
'''
绝对导入
glance/
├── __init__.py from glance import api
from glance import cmd
from glance import db
├── api
│ ├── __init__.py from glance.api import policy
from glance.api import versions
│ ├── policy.py
│ └── versions.py
├── cmd from glance.cmd import manage
│ ├── __init__.py
│ └── manage.py
└── db from glance.db import models
├── __init__.py
└── models.py
相对导入
glance/
├── __init__.py from . import api #.表示当前目录
from . import cmd
from . import db
├── api
│ ├── __init__.py from . import policy
from . import versions
│ ├── policy.py
│ └── versions.py
├── cmd from . import manage
│ ├── __init__.py
│ └── manage.py from ..api import policy
#..表示上一级目录,想再manage中使用policy中的方法就需要回到上一级glance目录往下找api包,从api导入policy
└── db from . import models
├── __init__.py
└── models.py
单独导入包
单独导入包名称时不会导入包中所有包含的所有子模块,如下所示:
解决方案
#glance/__init__.py
from . import cmd
#glance/cmd/__init__.py
from . import manage
测试结果
import glance之后直接调用模块中的方法
glance/
├── __init__.py from .api import *
from .cmd import *
from .db import *
├── api
│ ├── __init__.py __all__ = ['policy','versions']
│ ├── policy.py
│ └── versions.py
├── cmd __all__ = ['manage']
│ ├── __init__.py
│ └── manage.py
└── db __all__ = ['models']
├── __init__.py
└── models.py
import glance
policy.get()
软件开发规范
#=============>bin目录:存放执行脚本
#start.py
import sys,os
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from core import core
from conf import my_log_settings
if __name__ == '__main__':
my_log_settings.load_my_logging_cfg()
core.run()
#=============>conf目录:存放配置文件
#config.ini
[DEFAULT]
user_timeout = 1000
[egon]
password = 123
money = 10000000
[alex]
password = alex3714
money=10000000000
[yuanhao]
password = ysb123
money=10
#settings.py
import os
config_path=r'%s\%s' %(os.path.dirname(os.path.abspath(__file__)),'config.ini')
user_timeout=10
user_db_path=r'%s\%s' %(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),\
'db')
#my_log_settings.py
"""
logging配置
"""
import os
import logging.config
# 定义三种日志输出格式 开始
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
'[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'
# 定义日志输出格式 结束
logfile_dir = r'%s\log' %os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # log文件的目录
logfile_name = 'all2.log' # log文件名
# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir):
os.mkdir(logfile_dir)
# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name)
# log配置字典
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
},
'filters': {},
'handlers': {
#打印到终端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
#打印到文件的日志,收集info及以上的日志
'default': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'standard',
'filename': logfile_path, # 日志文件
'maxBytes': 1024*1024*5, # 日志大小 5M
'backupCount': 5,
'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了
},
},
'loggers': {
#logging.getLogger(__name__)拿到的logger配置
'': {
'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)传递
},
},
}
def load_my_logging_cfg():
logging.config.dictConfig(LOGGING_DIC) # 导入上面定义的logging配置
logger = logging.getLogger(__name__) # 生成一个log实例
logger.info('It works!') # 记录该文件的运行状态
if __name__ == '__main__':
load_my_logging_cfg()
#=============>core目录:存放核心逻辑
#core.py
import logging
import time
from conf import settings
from lib import read_ini
config=read_ini.read(settings.config_path)
logger=logging.getLogger(__name__)
current_user={'user':None,'login_time':None,'timeout':int(settings.user_timeout)}
def auth(func):
def wrapper(*args,**kwargs):
if current_user['user']:
interval=time.time()-current_user['login_time']
if interval < current_user['timeout']:
return func(*args,**kwargs)
name = input('name>>: ')
password = input('password>>: ')
if config.has_section(name):
if password == config.get(name,'password'):
logger.info('登录成功')
current_user['user']=name
current_user['login_time']=time.time()
return func(*args,**kwargs)
else:
logger.error('用户名不存在')
return wrapper
@auth
def buy():
print('buy...')
@auth
def run():
print('''
购物
查看余额
转账
''')
while True:
choice = input('>>: ').strip()
if not choice:continue
if choice == '1':
buy()
if __name__ == '__main__':
run()
#=============>db目录:存放数据库文件
#alex_json
#egon_json
#=============>lib目录:存放自定义的模块与包
#read_ini.py
import configparser
def read(config_file):
config=configparser.ConfigParser()
config.read(config_file)
return config
#=============>log目录:存放日志
#all2.log
[2017-07-29 00:31:40,272][MainThread:11692][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!]
[2017-07-29 00:31:41,789][MainThread:11692][task_id:core.core][core.py:25]
常见的模块使用
collections模块
这个模块实现专门的容器数据类型提供替代Python的通用内置容器中,dict,list, set,和tuple。
namedtuple() 生成可以使用名字来访问元素内容的tuple
deque 双端队列,可以快速的从另外一侧追加和推出对象
ChainMap 类似于dict的类,用于创建多个映射的单个视图
Counter 计数器,主要用来计数
OrderedDict 有序字典
defaultdict 带有默认值的字典
UserDict 围绕字典对象的包装器,以便于dict子类化
UserList 包装列表对象以便于列表子类化
UserString 包装字符串对象以便于字符串子类化
namedtuple()
# namedtuple('名称', [属性list]):
from collections import ChainMap
from collections import namedtuple
data = namedtuple('circle', ['x', 'y', 'z'])
p = data(1, 2, 3)
print(p.x) # 1
print(p.y) # 2
ChainMap()
模拟python内部查找链:
from collections import ChainMap
import builtins
pylookup = ChainMap(locals(), globals(), vars(builtins))
print(pylookup) # ChainMap({'__name__': '__main__', '__doc__': None, '__package__': None,...})
让用户指定的命令行参数优先于环境变量的示例,而环境变量优先于默认值:
import os, argparse
defaults = {'color': 'red', 'user': 'guest'}
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = {k: v for k, v in vars(namespace).items() if v}
combined = ChainMap(command_line_args, os.environ, defaults)
print(combined['color'])
print(combined['user'])
该ChainMap班不仅使更新(写入和删除),以在链中的第一个映射,同时查找将搜索上满链。但是,如果需要深度写入和删除,则很容易创建一个子类来更新链中更深层次的键:
class DeepChainMap(ChainMap):
'Variant of ChainMap that allows direct updates to inner scopes'
def __setitem__(self, key, value):
for mapping in self.maps:
if key in mapping:
mapping[key] = value
return
self.maps[0][key] = value
def __delitem__(self, key):
for mapping in self.maps:
if key in mapping:
del mapping[key]
return
raise KeyError(key)
>>> d = DeepChainMap({'zebra': 'black'}, {'elephant': 'blue'}, {'lion': 'yellow'})
>>> d['lion'] = 'orange' # update an existing key two levels down
>>> d['snake'] = 'red' # new keys get added to the topmost dict
>>> del d['elephant'] # remove an existing key one level down
>>> d # display result
DeepChainMap({'zebra': 'black', 'snake': 'red'}, {}, {'lion': 'orange'})
counter()
提供计数功能
>>> from collections import Counter
>>> cnt = Counter()
>>> for word in ['red', 'blue', 'red', 'green', 'blue']:
... cnt[word] += 1
...
>>> cnt
Counter({'red': 2, 'blue': 2, 'green': 1})
>>> cnt = Counter("YCajdbhasdbsjdnaFBDHA")
>>> cnt
Counter({'a': 3, 'd': 3, 'j': 2, 'b': 2, 's': 2, 'Y': 1, 'C': 1, 'h': 1, 'n': 1, 'F': 1, 'B': 1, 'D': 1, 'H': 1, 'A': 1})
>>> cnt['a']
3
from collections import Counter
c = Counter("aadndndndje") # 创建一个Counter对象
print(c) # Counter({'d': 4, 'n': 3, 'a': 2, 'j': 1, 'e': 1})
c.update("aaaaaaa")
print(c) # # Counter({'a': 9, 'd': 4, 'n': 3, 'j': 1, 'e': 1})
c.pop('a')
print(c) # Counter({'a': 9, 'd': 4, 'n': 3, 'j': 1, 'e': 1})
a = c.copy() # # Counter({'a': 9, 'd': 4, 'n': 3, 'j': 1, 'e': 1})
print(a)
test = Counter("aandndnd") # Counter({'n': 3, 'd': 3, 'a': 2})
print(test)
test.setdefault('e', 1) # Counter({'n': 3, 'd': 3, 'a': 2, 'e': 1})
test.setdefault('k') # Counter({'a': 2, 'n': 3, 'd': 3, 'e': 1, 'k': None})
test.popitem() # Counter({'n': 3, 'd': 3, 'a': 2, 'e': 1})
a = test.items() # dict_items([('a', 2), ('n', 3), ('d', 3), ('e', 1)])
key = test.keys() # dict_keys(['a', 'n', 'd', 'e'])
values = test.values() # dict_values([2, 3, 3, 1])
get_ = test.get('a', None) # 2 没有找到返会None
c = Counter("which")
print(c) # Counter({'h': 2, 'w': 1, 'i': 1, 'c': 1})
c.subtract('witch') # 减
print(c) # Counter({'h': 1, 'w': 0, 'i': 0, 'c': 0, 't': -1})
c.subtract(Counter("abcd"))
print(c) # Counter({'h': 1, 'w': 0, 'i': 0, 'c': -1, 't': -1, 'a': -1, 'b': -1, 'd': -1})
print(c) # Counter({'h': 1, 'w': 0, 'i': 0, 'c': -1, 't': -1, 'a': -1, 'b': -1, 'd': -1})
a = c.elements()
print(list(a)) # ['h'] 返回个数大于0值的key
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c + d # c[x] + d[x] Counter({'a': 4, 'b': 3})
c - d # subtract(只保留正数计数的元素)Counter({'a': 2})
c & d # 交集: min(c[x], d[x]) Counter({'a': 1, 'b': 1})
c | d # 并集: max(c[x], d[x]) Counter({'a': 3, 'b': 2})
c = Counter('abracadabra')
c.most_common() # [('a', 5), ('r', 2), ('b', 2), ('c', 1), ('d', 1)]
a =c.most_common(3) # [('a', 5), ('r', 2), ('b', 2)]
sum(c.values()) # 所有计数的总数
c.clear() # 重置Counter对象,注意不是删除
list(c) # 将c中的键转为列表
set(c) # 将c中的键转为set
dict(c) # 将c中的键值对转为字典
c.items() # 转为(elem, cnt)格式的列表
# Counter(dict(list_of_pairs)) # 从(elem, cnt)格式的列表转换为Counter类对象
c.most_common()[:-n:-1] # 取出计数最少的n个元素
c += Counter() # 移除0和负值
deque对象
基本操作:
>>> from collections import deque
>>> d = deque('ghi') # make a new deque with three items
>>> for elem in d: # iterate over the deque's elements
... print(elem.upper())
G
H
I
>>> d.append('j') # add a new entry to the right side
>>> d.appendleft('f') # add a new entry to the left side
>>> d # show the representation of the deque
deque(['f', 'g', 'h', 'i', 'j'])
>>> d.pop() # return and remove the rightmost item
'j'
>>> d.popleft() # return and remove the leftmost item
'f'
>>> list(d) # list the contents of the deque
['g', 'h', 'i']
>>> d[0] # peek at leftmost item
'g'
>>> d[-1] # peek at rightmost item
'i'
>>> list(reversed(d)) # list the contents of a deque in reverse
['i', 'h', 'g']
>>> 'h' in d # search the deque
True
>>> d.extend('jkl') # add multiple elements at once
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> d.rotate(1) # right rotation
>>> d
deque(['l', 'g', 'h', 'i', 'j', 'k'])
>>> d.rotate(-1) # left rotation
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> deque(reversed(d)) # make a new deque in reverse order
deque(['l', 'k', 'j', 'i', 'h', 'g'])
>>> d.clear() # empty the deque
>>> d.pop() # cannot pop from an empty deque
Traceback (most recent call last):
File "<pyshell#6>", line 1, in -toplevel-
d.pop()
IndexError: pop from an empty deque
>>> d.extendleft('abc') # extendleft() reverses the input order
>>> d
deque(['c', 'b', 'a'])
想查看更多关于collections的使用,请点击这里查看
时间模块(time)
表示时间的三种方式
在Python中,通常有这三种方式来表示时间:时间戳、元组(struct_time)、格式化的时间字符串:
(1)时间戳(timestamp) :通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。我们运行“type(time.time())”,返回的是float类型。
(2)格式化的时间字符串(Format String): ‘1999-12-06’
符号 | 解释 | 符号 | 解释 |
---|---|---|---|
%y | 两位数的年份表示(00-99 | %B | 本地完整的月份名称 |
%Y | 四位数的年份表示(000-9999) | %c | 本地相应的日期表示和时间表示 |
%m | 月份(01-12) | %j | 年内的一天(001-366) |
%H | 24小时制小时数(0-23) | %p | 本地A.M.或P.M.的等价符 |
%I | 12小时制小时数(01-12) | %U | 一年中的星期数(00-53)星期天为星期的开始 |
%M | 分钟数(00=59) | %w | 星期(0-6),星期天为星期的开始 |
%S | 秒(00-59) | %W | 一年中的星期数(00-53)星期一为星期的开始 |
%a | 本地简化星期名称 | %x | 本地相应的日期表示 |
%A | 本地完整星期名称 | %X | 本地相应的时间表示 |
%b | 本地简化的月份名称 | %Z | 当前时区的名称 |
%% | 转义% |
(3)元组(struct_time) :struct_time元组共有9个元素共九个元素:(年,月,日,时,分,秒,一年中第几周,一年中第几天等)
索引(Index) | 属性(Attribute) | 值(Values) |
---|---|---|
0 | tm_year(年) | 比如2011 |
1 | tm_mon(月) | 1 - 12 |
2 | tm_mday(日) | 1 - 31 |
3 | tm_hour(时) | 0 - 23 |
4 | tm_min(分) | 0 - 59 |
5 | tm_sec(秒) | 0 - 60 |
6 | tm_wday(weekday) | 0 - 6(0表示周一) |
7 | tm_yday(一年中的第几天) | 1 - 366 |
8 | tm_isdst(是否是夏令时) | 默认为0 |
time模块上的基本操作
1、time() # 时间戳
>>> import time
>>> time.time()
1542534790.7437017
>>> time.time()
1542534801.832316
2.localtime() # 获取当前时间信息。包含年月日时分秒等等。返回结果以元组的形式返回
>>> time.localtime()
time.struct_time(tm_year=2018, tm_mon=11, tm_mday=18, tm_hour=17, tm_min=53, tm_sec=42, tm_wday=6, tm_yday=322, tm_isdst=0)
3、strftime() # 它可以将localtime()中获取的时间元组转换为自定义的日期时间格式进行。
>>> time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
'2018-11-18 17:56:17'
>>> time.strftime('%Y-%m-%d %X',time.localtime())
'2018-11-18 18:42:16'
>>> time.strftime('%Y-%m-%d %X')
'2018-11-18 18:42:41'
4、gmtime() # 可以将时间秒转换为日期时间,此时日期和时间表示的是标准时间,北京时间为标准时间加上8个小时。
>>> time.gmtime() # #UTC时间,与英国伦敦当地时间一致
time.struct_time(tm_year=2018, tm_mon=11, tm_mday=18, tm_hour=11, tm_min=37, tm_sec=55, tm_wday=6, tm_yday=322, tm_isdst=0)
>>> time.gmtime(30000000)
time.struct_time(tm_year=1970, tm_mon=12, tm_mday=14, tm_hour=5, tm_min=20, tm_sec=0, tm_wday=0, tm_yday=348, tm_isdst=0)
5、asctime()和ctime() # 个都会返回固定格式的当前日期和时间,但两个接收的参数不同。asctime()接收的是元组格式的日期时间,而ctime()接收的是秒。然后都返回本地的格式化后的日期时间。
>>> time.time()
1542541318.1768582
>>> time.ctime(1542541318.1768582)
'Sun Nov 18 19:41:58 2018'
>>> time.asctime()
'Sun Nov 18 19:42:41 2018'
6.mktime() # 将元组形式的日期时间转换为秒的形式。
>>> time.mktime(time.gmtime())
1542512466.0
7、strptime() # #time.strptime(时间字符串,字符串对应格式)
>>> time.gmtime()
time.struct_time(tm_year=2018, tm_mon=11, tm_mday=18, tm_hour=11, tm_min=56, tm_sec=45, tm_wday=6, tm_yday=322, tm_isdst=0)
>>> time.strptime("07/24/2017","%m/%d/%Y")
time.struct_time(tm_year=2017, tm_mon=7, tm_mday=24, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=205, tm_isdst=-1)
>>> time.strptime('2018_11_18', "%Y_%m_%d")
time.struct_time(tm_year=2018, tm_mon=11, tm_mday=18, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=6, tm_yday=322, tm_isdst=-1)
>>> time.strptime('2018-11-18', '%Y-%m-%d')
time.struct_time(tm_year=2018, tm_mon=11, tm_mday=18, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=6, tm_yday=322, tm_isdst=-1)
几种格式之间的转换
random模块
random模块用于生成随机数。
>>> import random
>>> random.random()
0.1069376372957338
>>> random.random()
0.17347975880641364
>>> random.random() # 0-1的数字
0.24961236907060436
>>> random.uniform(1,3) # 大于1小于3的小数
2.246218695340162
>>> random.uniform(1,3) # 大于1小于3的小数
1.167361813902361
>>> random.randint(1, 5) # 大于等于1小于等于5的整数
5
>>> random.randint(1, 5)
4
>>> random.randrange(1, 10)
3
>>> random.randrange(1, 10) # 大于等于1其小于10的数
3
>>> random.randrange(1, 10)
8
>>> random.randrange(1, 10, 2) # 大于等于1且小于10之间的奇数
1
>>> random.randrange(1, 10, 2)
7
>>> items = [1, 3, 5, 7 , 9]
>>> random.shuffle(items) # 打乱次序
>>> items
[7, 9, 5, 3, 1]
>>> random.choice([[1], 2, [3, 4, 5]]) # [[1], 2, [3, 4, 5]中随机返回一个
2
>>> random.choice([[1], 2, [3, 4, 5]])
[3, 4, 5]
>>> random.sample([1, '456', [4, 5], "and"], 2) # 在[1, '456', [4, 5], "and"]中任意组合两个
['and', 1]
>>> random.sample([1, '456', [4, 5], "and"], 2)
[1, [4, 5]]
4位验证码的实现
import random
def verification():
code = ""
for i in range(4):
number = random.randint(0, 9)
alpha = chr(random.randint(65, 90))
alpha1 = chr(random.randint(97, 122))
add = random.choice([number, alpha1, alpha])
code = ''.join([code, str(add)])
return code
a = verification()
print(a) # 89kM
os模块
import os
# 1.切换路径=============
d = os.getcwd() #获取当前的工作路径
os.chdir('D:\\')#目录的切换
print(os.getcwd())
# (切换过去怎么回来呢?再chdir一下就回来了)
os.chdir(d)
print(os.getcwd())
# 2.执行系统命令=============
# system和popen都是执行系统命令的,但是popen比较好用,因为它有返回值
os.system('dir') #显示的是gbk的编码,
# 解决system乱码的方法
ret = os.popen('dir') #popen是有返回值的,而且自己转码了
print(ret.read())
# 3.创建文件夹=和创建文件==========
os.mkdir('temp') #生成一个文件夹,,只能生成一个
os.mkdir(r'temp2\inner') #这样就报错了
os.makedirs(r'temp1\inner',exist_ok=True) #创建多级目录
os.makedirs(r'temp1\inner\inner2',exist_ok=True) #创建多级目录
# 那么如果文件夹已经存在了,就报错了,那我如果不想
# 让报错(就是假如存在,就不创建也不报错),那么就加上exist_ok=True
# 创建文件
f = open(r'temp1\inner\file','w')
f.close()
# 4.======重命名文件夹=====
os.rename(r'temp1\inner\inner2','temp1\inner\haiyan')
# 5.=====删除文件夹和删除文件=========
# 先删文件,
os.remove(r'temp1\inner\file')
# 再删文件夹
os.removedirs(r'temp1\inner\haiyan') #删除一个文件夹的时候,如果上一级的文件夹是空的,就一并删除了。以此类推
os.rmdir((r'temp1\inner') )#只删除一个文件夹
# 6.子目录========
print(os.listdir(os.getcwd())) #打印当前目录下的目录
print(os.walk(os.getcwd())) #<generator object walk at 0x00000000021C6728>
ret = os.walk(os.getcwd()) #拿到的东西比较多,如果你关心子目录下的东西,就用walk
print(list(ret))
# 7.====获取文件或者目录的信息的结构说明========
print(os.stat('temp'))
# st_atime:上次访问的时间
# st_mtime:最后一次修改的时间
# st_ctime:最新的更新时间
print(os.sep) # 打印的是\
print(os.getcwd())
file_path = '%s%s%s'%(os.getcwd(),os.sep,'filename') #拼接一个路径(方式一)
print(file_path)
print(os.path.join(os.getcwd(),'filename'))#拼接一个路径(方式二)
# 8.====字符串指示当前使用平台
print(os.name) #如果是win,则打印的是nt 如果是,linux,打印poxis
# 应用场景:当你输入命令的时候,要判断是win系统还是 linux系统。就可以用
# os.name去判断了
# 9.===获取系统环境变量=====
print(os.environ)
# 10.路径相关的=======
print(os.path.abspath('namedtuple.py'))
print(os.path.dirname(os.path.abspath('namedtuple.py')))
print(os.path.dirname(os.path.dirname(os.path.abspath('namedtuple.py'))))
print(os.path.exists(os.path.abspath('namedtuple.py')))
os.makedirs('dirname1/dirname2') # 可生成多层递归目录
os.removedirs('dirname1') # 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.mkdir('dirname') # 生成单级目录;相当于shell中mkdir dirname
os.rmdir('dirname') # 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.listdir('dirname') # 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.remove() # 删除一个文件
os.rename("oldname","newname") # 重命名文件/目录
os.stat('path/filename') # 获取文件/目录信息
os.system("bash command") # 运行shell命令,直接显示
os.popen("bash command).read() # 运行shell命令,获取执行结果
os.getcwd() # 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname") # 改变当前脚本工作目录;相当于shell下cd
os.path
os.path.abspath(path) # 返回path规范化的绝对路径
os.path.split(path) # 将path分割成目录和文件名二元组返回
os.path.dirname(path) # 返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path) # 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
os.path.exists(path) # 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path) # 如果path是绝对路径,返回True
os.path.isfile(path) # 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path) # 如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]]) # 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.getatime(path) # 返回path所指向的文件或者目录的最后访问时间
os.path.getmtime(path) # 返回path所指向的文件或者目录的最后修改时间
os.path.getsize(path) # 返回path的大小
os.sep # 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
os.linesep # 输出当前平台使用的行终止符,win下为"\r\n",Linux下为"\n"
os.pathsep # 输出用于分割文件路径的字符串 win下为;,Linux下为:
os.system('cls') # 清除屏幕
sys模块
import sys
>>> dir(sys) # 查看模块的属性和方法
['__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_enablelegacywindowsfsencoding', '_getframe', '_git', '_home', '_xoptions', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'getwindowsversion', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'set_asyncgen_hooks', 'set_coroutine_wrapper', 'setcheckinterval', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions', 'winver']
>>> sys.version # 获取python解释器的版本信息
'3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 10:22:32) [MSC v.1900 64 bit (AMD64)]'
>>> sys.version_info # 包含版本的五个组建的元组 major=3, minor=6, micro=4, releaselevel='final', serial=0
sys.version_info(major=3, minor=6, micro=4, releaselevel='final', serial=0)
>>> sys.platform # 获取操作系统的平台名称
'win32'
>>> sys.api_version
1013 # 解释器的c的api版本,在调试python和扩展模块之间的版本冲突有用
>>> sys.copyright # 获取python相关的信息
'Copyright (c) 2001-2017 Python Software Foundation.\nAll Rights Reserved.\n\nCopyright (c) 2000 BeOpen.com.\nAll Rights Reserved.\n\nCopyright (c) 1995-2001 Corporation for National Research Initiatives.\nAll Rights Reserved.\n\nCopyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.\nAll Rights Reserved.'
>>> sys.builtin_module_names # 获取python内建模块的名称(字符串元组)
('_ast', '_bisect', '_blake2', '_codecs', '_codecs_cn', '_codecs_hk', '_codecs_iso2022', '_codecs_jp', '_codecs_kr', '_codecs_tw', '_collections', '_csv', '_datetime', '_findvs', '_functools', '_heapq', '_imp', '_io', '_json', '_locale', '_lsprof', '_md5', '_multibytecodec', '_opcode', '_operator', '_pickle', '_random', '_sha1', '_sha256', '_sha3', '_sha512', '_signal', '_sre', '_stat', '_string', '_struct', '_symtable', '_thread', '_tracemalloc', '_warnings', '_weakref', '_winapi', 'array', 'atexit', 'audioop', 'binascii', 'builtins', 'cmath', 'errno', 'faulthandler', 'gc', 'itertools', 'marshal', 'math', 'mmap', 'msvcrt', 'nt', 'parser', 'sys', 'time', 'winreg', 'xxsubtype', 'zipimport', 'zlib')
>>> sys.executable # python解释器全名(包括路径名)
'G:\\Sofeware\\Anaconda\\python.exe'
>>> sys.path # 1、记录导入模块时的搜索路径 2、字符串列表 3、sys.path中的一一个''表示是当前目录4、python 解释器根据sys.path中的路径先后顺序,搜索路径
['', 'G:\\Sofeware\\Anaconda\\python36.zip', 'G:\\Sofeware\\Anaconda\\DLLs', 'G:\\Sofeware\\Anaconda\\lib', 'G:\\Sofeware\\Anaconda', 'G:\\Sofeware\\Anaconda\\lib\\site-packages', 'G:\\Sofeware\\Anaconda\\lib\\site-packages\\win32', 'G:\\Sofeware\\Anaconda\\lib\\site-packages\\win32\\lib', 'G:\\Sofeware\\Anaconda\\lib\\site-packages\\Pythonwin']
>>>sys.path 来源 # 1、包括导入脚本的目录(即当前目录)2、pythonpath(一个目录列表,其形式同shell变量path的语法)
>>>可以在程序中修改sys.path,增加搜索路径 - sys.path.append("自定义的模块的路径")
>>> sys.stdin # 用于交互式的输入(包括input的调用)
<_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'>
>>> sys.stdout # 用于print()和expression语句的输出以及input()的提示
<_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
>>> sys.stderr # 显示解释器自己的提示和它的错误消息
<_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
>>> sys.modules # 记录了已经导入系统的大模块
{'builtins': <module 'builtins' (built-in)>, 'sys': <module 'sys' (built-in)>, '_frozen_importlib': <module 'importlib._bootstrap' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_thread': <module '_thread' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_frozen_importlib_external': <module 'importlib._bootstrap_external' (frozen)>, ......
>>>>
# 使用stdin输入信息 *
>>> for line in sys.stdin:
... print(line)
...
Yangchang
Yangchang
>>>>
# 使用stdin输出信息
>>> sys.stdout.write("Yang")
Yang4
>>> sys.stdout.write("Yang\n")
Yang
5
>>> r = sys.stdout.write("Yang\n")
Yang
>>> r
5
>>> sys.argv[0] # 在外部向程序内部传递参数
''
sys.exit(n) # 执行到主程序末尾,解释器自动退出,但是如果需要中途退出程序,可以调用sys.exit函数,带有一个可选的整数参数返回给调用它的程序,表示你可以在主程序中捕获对sys.exit的调用。(0是正常退出,其他为异常)
import sys
def exit_(values):
print("sys.exit() test, Values is:", values)
print("begin")
try:
sys.exit(1)
except SystemExit as e:
exit_(e)
print("end!")
# begin
# sys.exit() test, Values is: 1
# end!
>>> sys.getdefaultencoding() # 获取系统当前编码,一般默认为ascii。
'utf-8'
>>> sys.getfilesystemencoding() #获取文件系统使用编码方式
'utf-8'
序列化(json)模块
什么叫序列化——将原本的字典、列表等内容转换成一个字符串的过程就叫做序列化。
序列化的目的
1、以某种存储形式使自定义对象持久化;
2、将对象从一个地方传递到另一个地方。
3、使程序更具维护性。
loads、dumps、load和dump
import json
# dumps and loads使用
dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
# 序列化 将字典转化位字符串
str_dic = json.dumps(dic) # 序列化:将一个字典转换成一个字符串 {"k1": "v1", "k2": "v2", "k3": "v3"}
print(type(str_dic)) # <class 'str'>
# json转换完的字符串类型的字典中的字符串是由""表示的
# 反序列化 将字符串转换化为字典
dic_str = json.loads(str_dic)
print(type(dic_str), dic_str) # <class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
# 注意,要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示
# 嵌套(list, tuple, dict)使用
nest = [1, "a", [1, "list", 2.0], ("a", "b"), {"dic": "dict", "x": 1}]
str_nest = json.dumps(nest)
print(type(str_nest), str_nest) # <class 'str'> [1, "a", [1, "list", 2.0], ["a", "b"], {"dic": "dict", "x": 1}]
list_nest = json.loads(str_nest)
print(type(list_nest), list_nest) # <class 'list'> [1, 'a', [1, 'list', 2.0], ['a', 'b'], {'dic': 'dict', 'x': 1}]
# load and dump使用
f = open("file", mode='w', encoding="utf-8")
json.dump(dic, f) # dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件
f.close()
read_file = open('file', mode='r', encoding='utf-8')
dict_ = json.load(read_file) # load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回
read_file.close()
print(type(dict_), dict_) # <class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
# ensure_ascill关键字参数
import json
f = open('file', 'w')
json.dump({'国籍': '中国'},f)
ret = json.dumps({'国籍': '中国'})
f.write(ret+'\n')
json.dump({'国籍': '美国'}, f, ensure_ascii=False)
ret = json.dumps({'国籍': '美国'},ensure_ascii=False)
f.write(ret+'\n')
f.close()
import json
data = {'username': ['Yang', 'Xia'], 'sex': 'male', 'age': 20}
json_dic2 = json.dumps(data,sort_keys=True, indent=2, separators=(',', ':'),ensure_ascii=False)
print(json_dic2)
'''
{
"age":20,
"sex":"male",
"username":[
"Yang",
"Xia"
]
}
'''
python3.7.1文档中的例子
>>> import json
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> print(json.dumps("\"foo\bar"))
"\"foo\bar"
>>> print(json.dumps('\u1234'))
"\u1234"
>>> print(json.dumps('\\'))
"\\"
>>> print(json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True))
{"a": 0, "b": 0, "c": 0}
>>> from io import StringIO
>>> io = StringIO()
>>> json.dump(['streaming API'], io)
>>> io.getvalue()
'["streaming API"]'
>>> import json
>>> json.dumps([1, 2, 3, {'4': 5, '6': 7}], separators=(',', ':'))
'[1,2,3,{"4":5,"6":7}]'
>>> import json
>>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4))
{
"4": 5,
"6": 7
}
>>> import json
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
['foo', {'bar': ['baz', None, 1.0, 2]}]
>>> json.loads('"\\"foo\\bar"')
'"foo\x08ar'
>>> from io import StringIO
>>> io = StringIO('["streaming API"]')
>>> json.load(io)
['streaming API']
>>> import json
>>> def as_complex(dct):
... if '__complex__' in dct:
... return complex(dct['real'], dct['imag'])
... return dct
...
>>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
... object_hook=as_complex)
(1+2j)
>>> import decimal
>>> json.loads('1.1', parse_float=decimal.Decimal)
Decimal('1.1')
>>> import json
>>> class ComplexEncoder(json.JSONEncoder):
... def default(self, obj):
... if isinstance(obj, complex):
... return [obj.real, obj.imag]
... # Let the base class default method raise the TypeError
... return json.JSONEncoder.default(self, obj)
...
>>> json.dumps(2 + 1j, cls=ComplexEncoder)
'[2.0, 1.0]'
>>> ComplexEncoder().encode(2 + 1j)
'[2.0, 1.0]'
>>> list(ComplexEncoder().iterencode(2 + 1j))
['[2.0', ', 1.0', ']']
想了解更多,请点击这里
json & pickle 模块
json和pickle模块的区别:
- json,用于字符串 和 python数据类型间进行转换
- pickle,用于python特有的类型 和 python的数据类型间进行转换
json:json是一种所有的语言都可以识别的数据结构.如果将一个字典或者序列化成了一个json存在文件里,那么java代码或者js代码也可以拿来用。
**pickle: **用pickle进行序列化,其他语言是不能识别的, 如果python对这个数据进行反序列化的话,那么就可以使用pickle。
import pickle
dic = {'k1':'v1','k2':'v2','k3':'v3'}
str_dic = pickle.dumps(dic)
print(str_dic) #一串二进制内容
dic2 = pickle.loads(str_dic)
print(dic2) #字典
import time
struct_time = time.localtime(1000000000)
print(struct_time)
f = open('pickle_file','wb')
pickle.dump(struct_time,f)
f.close()
f = open('pickle_file','rb')
struct_time2 = pickle.load(f)
print(struct_time2.tm_year)
正则表达式
正则表达式为高级的文本模式匹配、抽取、与/或文本形式的搜索和替换功能提供了基础。 简单地说,正则表达式(简称为 regex)是一些由字符和特殊符号组成的字符串,它们描述了 模式的重复或者表述多个字符,于是正则表达式能按照某种模式匹配一系列有相似特征的字 符串
特殊符号和字符
使用择一匹配符号匹配多个正则表达式模式
匹配的管道符号(|),也就是键盘上的竖线,表示一个“从多个模式中选择其一”的操作。它用于分割不同的正则表达式。
择一匹配有时候也称作并(union)或者逻辑(logical OR)
匹配任意单个字符
点号或者句点(.)符号匹配除了换行符\n 以外的任何字符无论字母、数字、空格(并不包括“\n”换行符)、可打印字符、不可打印字符,还是一个符号,使用点号都能够匹配它们。
从字符串起始或者结尾或者单词边界匹配
还有些符号和相关的特殊字符用于在字符串的起始和结尾部分指定用于搜索的模式。如
果要匹配字符串的开始位置,就必须使用脱字符(^)或者特殊字符\A(反斜线和大写字母 A)。
后者主要用于那些没有脱字符的键盘(例如,某些国际键盘)。同样,美元符号($)或者\Z将用于匹配字符串的末尾位置。
特殊字符\b 和\B 可以用来匹配字符边界。而两者的区别在于\b 将用于匹配一个单词的边
界,这意味着如果一个模式必须位于单词的起始部分,就不管该单词前面(单词位于字符串
中间)是否有任何字符(单词位于行首)。同样,\B 将匹配出现在一个单词中间的模式(即,
不是单词边界)。
创建字符集
尽管句点可以用于匹配任意符号,但某些时候,可能想要匹配某些特定字符。正因如此,发明了方括号。该正则表达式能够匹配一对方括号中包含的任何字符
关于[cr][23][dp][o2]这个正则表达式有一点需要说明:如果仅允许“r2d2”或者“c3po”
作为有效字符串,就需要更严格限定的正则表达式。因为方括号仅仅表示逻辑或的功能,所以使用方括号并不能实现这一限定要求。唯一的方案就是使用择一匹配,例如:r2d2|c3po。
限定范围和否定
除了单字符以外,字符集还支持匹配指定的字符范围。方括号中两个符号中间用连字符(-)连接,用于指定一个字符的范围;例如,A-Z、a-z 或者 0-9 分别用于表示大写字母、小写字母和数值数字。这是一个按照字母顺序的范围,所以不能将它们仅仅限定用于字母和十进制数字上。另外,如果脱字符(^)紧跟在左方括号后面,这个符号就表示不匹配给定字符集中的任何一个字符。
使用闭包操作符实现存在性和频数匹配
最常用的正则表达式符号,即特殊符号*、+和?,所有这些都可以用于匹配一
个、多个或者没有出现的字符串模式。星号或者星号操作符(*)将匹配其左边的正则表达式出现零次或者多次的情况(在计算机编程语言和编译原理中,该操作称为 Kleene 闭包)。加号(+)操作符将匹配一次或者多次出现的正则表达式(也叫做正闭包操作符),问号(?)
操作符将匹配零次或者一次出现的正则表达式。
还有大括号操作符({}),里面或者是单个值或者是一对由逗号分隔的值。这将最终精
确地匹配前面的正则表达式 N 次(如果是{N})或者一定范围的次数;例如,{M , N}将匹
配 M~N 次出现。这些符号能够由反斜线符号转义;\*匹配星号,等等。
表示字符集的特殊字符
有一些特殊字符能够表示字符集。与使用“0-9”这个范围表示十进制数相比,可以简单地使用 d 表示匹配任何十进制数字。另一个特殊字符(\w)能够用于表示全部字母数字的字符集,相当于[A-Za-z0-9_]的缩写形式,\s 可以用来表示空格字符。这些特殊字符的大写版本表示不匹配;例如,\D 表示任何非十进制数(与[^0-9]相同),等等。
使用圆括号指定分组
有些时候,我们可能会对之前匹配成功的数据更感兴趣。我们不仅想要知道整个字符串是否匹配我们的标准,而且想要知道能否提取任何已经成功匹配的特定字符串或者子字符串.
当使用正则表达式时,一对圆括号可以实现以下任意一个(或者两个)功能:
• 对正则表达式进行分组;
• 匹配子组。
扩展表示法
它们是以问号开始(?…)它们通常用于在判断匹配之前提供标记,实现一个前视(或者后视)匹配,或者条件检查。
常见的正则表达式
常用元字符
代码 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
常用限定符
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
重复n次 | |
重复n次或更多次 | |
重复n到m次 |
常用反义词
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
re模块
常见的正则表达式属性
匹配对象以及 group()和 groups()方法
当处理正则表达式时,除了正则表达式对象之外,还有另一个对象类型:匹配对象。这些是成功调用 match()或者 search()返回的对象。匹配对象有两个主要的方法:group()和groups()。
group()要么返回整个匹配对象,要么根据要求返回特定子组。groups()则仅返回一个包含唯一或者全部子组的元组。如果没有子组的要求,那么当group()仍然返回整个匹配时,groups()返回一个空元组。
match()函数试图从字符串的起始部分对模式进行匹配。如果匹配成功,就返回一个匹配对象;如果匹配失败,就返回 None,匹配对象的 group()方法能够用于显示那个成功的匹配.
import re
a = re.match("test1", "test123") # 模式匹配字符串
if a is not None:
b = a.groups() # 如果匹配成功,就输出匹配内容
print(b) # ()
import re
a = re.match("test1", "test123") # 模式匹配字符串
if a is not None:
b = a.group() # 如果匹配成功,就输出匹配内容
print(b) # test1
>>> re.match("Y", "Yang") # 匹配成功
<_sre.SRE_Match object; span=(0, 1), match='Y'>
>>> re.match('foo', 'food on the table').group()
'foo'
如果匹配失败,将会抛出 AttributeError 异常。
使用search()在一个字符串中查找模式(搜索与匹配的对比)
search()的工作方式与 match()完全一致,不同之处在于 search()会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。如果搜索到成功的匹配,就会返回一个匹配对象;否则,返回 None
>>> m = re.match('foo', 'seafood') #匹配失败
>>> if m is not None:m.group()
...
>>> m = re.search('foo', 'seafood') # 匹配成功
>>> if m is not None:m.group()
...
'foo
匹配多个字符串
>>> bt = 'bat|bet|bit' # 正则表达式模式 : bat 、 bet 、 bit
>>> m = re.match(bt, 'bat') # 'bat' 是一个匹配
>>> if m is not None: m.group()
...
'bat'
>>> m = re.match(bt, 'blt') # 对于 'blt' 没有匹配
>>> if m is not None: m.group()
...
>>> m = re.match(bt, 'He bit me!') # 不能匹配字符串
>>> if m is not None: m.group()
...
>>> m = re.search(bt, 'He bit me!') # 通过搜索查找 'bit'
>>> if m is not None: m.group()
...
'bit'
匹配任何单个字符
点号(.)不能匹配一个换行符\n 或者非字符
>>> anyend = '.end'
>>> m = re.match(anyend, 'bend') # 点号匹配 'b'
>>> if m is not None: m.group()
...
'bend'
>>> m = re.match(anyend, 'end') # 不匹配任何字符
>>> if m is not None: m.group()
...
>>> m = re.match(anyend, '\nend') # 除了 \n 之外的任何字符
>>> if m is not None: m.group()
...
>>> m = re.search('.end', 'The end.')# 在搜索中匹配 ' '
>>> if m is not None: m.group()
...
' end'
正则表达式中搜索一个真正的句点(小数点),而我们通过使用一个反斜线对句点的功能进行转义:
>>> patt314 = '3.14' # 表示正则表达式的点号
>>> pi_patt = '3\.14' # 表示字面量的点号 (dec. point)
>>> m = re.match(pi_patt, '3.14') # 精确匹配
>>> if m is not None: m.group()
...
'3.14'
>>> m = re.match(patt314, '3014') # 点号匹配 '0'
>>> if m is not None: m.group()
...
'3014'
>>> m = re.match(patt314, '3.14') # 点号匹配 '.'
>>> if m is not None: m.group()
...
'3.14'
创建字符集([ ])
[cr][23][dp][o2],以及它们与 r2d2|c3po 之间的差别, 后者比前者更加的严格。
>>> m = re.match('[cr][23][dp][o2]', 'c3po')# 匹配 'c3po'
>>> if m is not None: m.group()
...
'c3po'
>>> m = re.match('[cr][23][dp][o2]', 'c2do')# 匹配 'c2do'
>>> if m is not None: m.group()
...
'c2do'
>>> m = re.match('r2d2|c3po', 'c2do')# 不匹配 'c2do'
>>> if m is not None: m.group()
...
>>> m = re.match('r2d2|c3po', 'r2d2')# 匹配 'r2d2'
>>> if m is not None: m.group()
...
'r2d2'
重复、特殊字符以及分组
我们曾看到过一个关于简单电子邮件地址的正则表达式(\w+@\w+.com)。或许我们想要匹配比这个正则表达式所允许的更多邮件地址。为了在域名前添加主机名称支持,例如 www.xxx.com,仅仅允许 xxx.com 作为整个域名,必须修改现有的正则表达式。为了表示主机名是可选的,需要创建一个模式来匹配主机名(后面跟着一个句点),使用“?”操作符来表示该模式出现零次或者一次,然后按照如下所示的方式,插入可选的正则表达式到之前的正则表达式中:\w+@(\w+.)?\w+.com。从下面的示例中可见,该表达式允许.com 前面有一个或者两个名称。
import re
email = '\w+@(\w+\.)?\w+\.com'
m = re.match(email, '123456789@qq.com') # 123456789@qq.xxx.com
if m is not None:
a = m.group()
print(a) # 123456789@qq.com , 123456789@qq.xxx.com
以下模式来进一步扩展该示例,允许任意数量的中间子域名存在。例如:xxx@xx.xxx.xxx.xxx......com @后面的数量可以不断添加
import re
email = '\w+@(\w+\.)*\w+\.com'
m = re.match(email, '123456789@qq.xxx.xxx.xxxx.xxxxx.com')
if m is not None:
a = m.group()
print(a) # 123456789@qq.xxx.xxx.xxxx.xxxxx.com
import re
email = '\w+@(\w+\.)*\w+\.com'
m = re.match(email, '123456789@qq.xxxxx.com')
if m is not None:
a = m.group()
print(a) # 123456789@qq.xxxxx.com
a = '\w\w\w\w-\d\d\d\d'
# m = re.match(a, "1234-4567")
# m = re.match(a, "aaaa-4567")
m = re.match(a, "aaaa-aaaa") # \d是数字 匹配失败
if m is not None:
a = m.group()
print(a) # 1234-4567, aaaa-4567
a = '(\w\w\w\w)-(\d\d\d\d)' # 加上'()-()'
m = re.match(a, '1234-4567')
print(m.group(1)) # 1234
print(m.group(2)) # 4567
print(m.group()) # 1234-4567
>>> m = re.match('ab', 'ab') # 没有子组
>>> m.group() # 完整匹配
'ab'
>>> m.groups() # 所有子组
()
>>> m = re.match('(ab)', 'ab') # 一个子组
>>> m.group() # 完整匹配
'ab'
>>> m.group(1) # 子组 1
'ab'
>>> m.groups() # 全部子组
('ab',)
>>>
>>> m = re.match('(a)(b)', 'ab') # 两个子组
>>> m.group() # 完整匹配
'ab'
>>> m.group(1) # 子组 1
'a'
>>> m.group(2) # 子组 2
'b'
>>> m.groups() # 所有子组
('a', 'b')
>>> m = re.match('(a(b))', 'ab') # 两个子组
>>> m.group() # 完整匹配
'ab'
>>> m.group(1) # 子组 1
'ab'
>>> m.group(2) # 子组 2
'b'
>>> m.groups() # 所有子组
('ab', 'b')
匹配字符串的起始和结尾以及单词边界
>>> m = re.search('^The', 'The end.') # 匹配
>>> if m is not None: m.group()
...
'The'
>>> m = re.search('^The', 'end. The') # 不作为起始
>>> if m is not None: m.group()
...
>>> m = re.search(r'\bthe', 'bite the dog') # 在边界
>>> if m is not None: m.group()
...
'the'
>>> m = re.search(r'\bthe', 'bitethe dog') # 有边界
>>> if m is not None: m.group()
...
>>> m = re.search(r'\Bthe', 'bitethe dog') # 没有边界
>>> if m is not None: m.group()
...
'the'
使用 findall()和 finditer()查找每一次出现的位置
findall()查询字符串中某个正则表达式模式全部的非重复出现情况。这与 search()在执行字符串搜索时类似,但与 match()和 search()的不同之处在于,findall()总是返回一个列表。如果 findall()没有找到匹配的部分,就返回一个空列表,但如果匹配成功,列表将包含所有成功的匹配部分(从左向右按出现顺序排列)
>>> re.findall('test', 'test') # 完全匹配
['test']
>>> re.findall('test', 'tes')
[]
>>> re.findall('test', 'test a')
['test']
>>> re.findall('car', 'scary')
['car']
>>> re.findall('car', 'carry the barcardi to the car')
['car', 'car', 'car']
>>> s = 'This and that.'
>>> re.findall(r'(th\w+) and (th\w+)', s, re.I)
[('This', 'that')]
>>> re.finditer(r'(th\w+) and (th\w+)', s,
... re.I).next().groups()
('This', 'that')
>>> re.finditer(r'(th\w+) and (th\w+)', s,
... re.I).next().group(1)
'This'
>>> re.finditer(r'(th\w+) and (th\w+)', s,
... re.I).next().group(2)
'that'
>>> [g.groups() for g in re.finditer(r'(th\w+) and (th\w+)',
... s, re.I)]
[('This', 'that')]
使用 sub()和 subn()搜索与替换
有两个函数/方法用于实现搜索和替换功能:sub()和 subn()。两者几乎一样,都是将某字符串中所有匹配正则表达式的部分进行某种形式的替换。用来替换的部分通常是一个字符串,但它也可能是一个函数,该函数返回一个用来替换的字符串。subn()和 sub()一样,但 subn()还返回一个表示替换的总数,替换后的字符串和表示替换总数的数字一起作为一个拥有两个元素的元组返回。
>>> re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n') # sub('想替换的内容','替换的内容','要替换的字符串') 返回替换后的字符串
'attn: Mr. Smith\n\nDear Mr. Smith,\n'
>>> re.subn('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')
('attn: Mr. Smith\n\nDear Mr. Smith,\n', 2) # 替换后的返回元组
>>> re.sub('[acd]', 'X', 'abcdef')
'XbXXef'
>>> re.subn('[acd]', 'X', 'abcdef')
('XbXXef', 3)
split()分隔字符串
>>> re.split(":", 'a:b:c')
['a', 'b', 'c']
>>> import re
>>> DATA = (
... 'Mountain View, CA 94040',
... 'Sunnyvale, CA',
... 'Los Altos, 94023',
... 'Cupertino 95014',
... 'Palo Alto CA',
... )
>>> for datum in DATA:
... print re.split(', |(?= (?:\d{5}|[A-Z]{2})) ', datum)
...
['Mountain View', 'CA', '94040']
['Sunnyvale', 'CA']
['Los Altos', '94023']
['Cupertino', '95014']
['Palo Alto', 'CA']
异常处理
什么是异常?
异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生一个异常。异常是Python对象,表示一个错误。
当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。为什么要进行异常处理python解析器去执行程序,检测到了一个错误时,触发异常,异常触发后且没被处理的情况下,程序就在当前异常处终止,后面的代码不会运行,谁会去用一个运行着突然就崩溃的软件。
所以你必须提供一种异常处理机制来增强你程序的健壮性与容错性。
python的标准异常如下
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
异常处理的基本格式
1.try...except...else的语法
try:
<语句> #运行别的代码
except <名字>:
<语句> #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句> #如果引发了'name'异常,获得附加的数据
else:
<语句> #如果没有异常发生
语法的实现的具体化
def input_():
num = input(">>>")
try:
a = int(num)
except ValueError:
print("你输入的格式有问题哦!")
else:
print("恭喜你输入成功")
input_()
2.万能异常Exception,它可以捕捉到任何的异常
s1 = "string"
try:
# int(s1)
num
except Exception as e:
print("异常了大哥!")
3.多分支的except
s1 = 'hello'
try:
int(s1)
except IndexError as e:
print(e)
except KeyError as e:
print(e)
except ValueError as e:
print(e)
4.异常的其他的机构:
s1 = 'hello'
try:
int(s1)
except IndexError as e:
print(e)
except KeyError as e:
print(e)
except ValueError as e:
print(e)
#except Exception as e: # 万能异常(放在最后)
# print(e)
else:
print('try内代码块没有异常则执行我')
finally:
print('无论异常与否,都会执行该模块,通常是进行清理工作')
5.主动触发异常
try:
raise TypeError("ValueError")
except Exception as e:
print("错误了大哥!error:",e)
"""错误了大哥!error: ValueError"""
6.自定义异常:
class EvaException(BaseException):
def __init__(self,msg):
self.msg=msg
def __str__(self):
return self.msg
try:
raise EvaException('类型错误')
except EvaException as e:
print("大哥错误了!",e)
"""大哥错误了! 类型错误"""
7.assert断言
assert断言是声明其布尔值必须为真的判定,如果发生异常就说明表达示为假。可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触发异常。
assert expression [, arguments]
assert 表达式 [, 参数]
a = 10
try:
assert a > 0
except AssertionError:
print("错误!")
else:
print("succeed")
类和OOP
初始面向对象
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。考虑周全什么时候处理什么东西。
向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。
面向对象的程序设计的
优点是:解决了程序的扩展性。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
创建类
使用 class 语句来创建一个新类,class 之后为类的名称并以冒号结尾:
class ClassName:
'类的帮助信息' #类文档字符串
class_suite #类体
实例:
class test: # 创建一个test类
'''
这个一个测试的类
'''
test_info = '****test****'
def __init__(self, name, age): # 属性 __init__(slef) 属性的初始化
self.age = age
self.name = name
def show(self): # 显示属性
print("name:{name} and age:{age}".format(name=self.name, age=self.age))
t = test("Yang", 12) # t:就是一个对象 test("Yang", 12):是进行属性参数是设置
t.show() # 对象调用类内部的方法
print(test.__name__) # 显示类名
print(test.__doc__) # 显示说明文档
print(test.__bases__) # 类所有父类构成的元组 (<class 'object'>,)
print(test.__dict__) # 类的字典属性: {'__module__': '__main__', '__doc__': '\n 这个一个测试的类\n...
print(test.__module__) # 类中所定义的模块 __main__
print(test.__class__) # 实例对应的类(仅新式类中) <class 'type'>
类名称空间与对象的名称空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
静态属性就是直接在类中定义的变量
动态属性就是定义在类中的方法
其中类的数据属性是共享给所有对象的
print(id(show.test_info)) # 1850407347568
print(id(s.test_info)) # 1850407347568
而类的动态属性是绑定到所有对象的
print(id(s.show)) # 1567752988040
print(id(show.show)) # 1567783533696
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
class Test: # 超类Test
pass
class Test1: # 超类Test1
pass
class A(Test): # A 继承于 Test
pass
class B(Test, Test1): # B 继承于 Test和 Test1
pass
print(B.__bases__) # (<class '__main__.Test'>, <class '__main__.Test1'>)
继承与抽象(先抽象再继承)
class Animal:
def __init__(self,name):
self.name = name
def eat(self):
print("%s 吃 " %self.name)
def drink(self):
print ("%s 喝 " %self.name)
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '猫'
def climb(self):
print('爬树')
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed='狗'
def look_after_house(self):
print('汪汪叫')
c1 = Cat('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时我们不可能从头开始写一个类B,这就是继承的思想。
class A:
data = "****test*****"
def __init__(self, name, age): # 姓名 年龄
self.name = name
self.age = age
def show(self): # 显示属性
print("name:{name},age:{age}".format(name=self.name, age=self.age))
class B(A):
pass
class C(A):
pass
b = B("Yang", 15)
print(b.data) # ****test*****
c = C("Xue", 20)
b.show() # name:Yang,age:15
c.show() # name:Xue,age:20
派生
然而子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class A:
data = "****test*****"
def __init__(self, name, age): # 姓名 年龄
self.name = name
self.age = age
def show(self): # 显示属性
print("name:{name},age:{age}".format(name=self.name, age=self.age))
class B(A):
def add_(self, num1, num2):
return num1 + num2
class C(A):
def sub_(self, num1, num2):
return num1 - num2
pass
b = B("Yang", 15)
print(b.data) # ****test*****
c = C("Xue", 20)
print("add result:", b.add_(12, 10)) # add result: 22
print("sub result:",c.sub_(12, 10)) # add result: 2
b.show() # name:Yang,age:15
c.show() # name:Xue,age:20
在python3中,子类执行父类的方法也可以直接用super方法.
class A:
def test(self):
print("A")
class B(A):
def b_test(self):
super().test()
print("B")
def test(self):
super().test()
print("B_test")
b= B()
b.b_test()
b.test()
'''
A
B
A
B_test
'''
抽象类与接口类
接口类
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Alipay:
'''
支付宝支付
'''
def pay(self,money):
print('支付宝支付了%s元'%money)
class Applepay:
'''
apple pay支付
'''
def pay(self,money):
print('apple pay支付了%s元'%money)
def pay(payment,money):
'''
支付函数,总体负责支付
对应支付的对象和要支付的金额
'''
payment.pay(money)
p = Alipay()
pay(p,200)
开发中容易出现的问题
class Alipay:
'''
支付宝支付
'''
def pay(self,money):
print('支付宝支付了%s元'%money)
class Applepay:
'''
apple pay支付
'''
def pay(self,money):
print('apple pay支付了%s元'%money)
class Wechatpay:
def fuqian(self,money):
'''
实现了pay的功能,但是名字不一样
'''
print('微信支付了%s元'%money)
def pay(payment,money):
'''
支付函数,总体负责支付
对应支付的对象和要支付的金额
'''
payment.pay(money)
p = Wechatpay()
pay(p,200) #执行会报错
接口初成:手动报异常:NotImplementedError来解决开发中遇到的问题
class Payment:
def pay(self):
raise NotImplementedError
class Wechatpay(Payment):
def fuqian(self,money):
print('微信支付了%s元'%money)
p = Wechatpay() #这里不报错
pay(p,200) #这里报错了
借用abc模块来实现接口
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self,money):
pass
class Wechatpay(Payment):
def fuqian(self,money):
print('微信支付了%s元'%money)
p = Wechatpay() #不调就报错
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
依赖倒置原则:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块.
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
抽象类
什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么会使用抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的
python抽象类的实现
#一切皆文件
import abc #利用abc模块实现抽象类
class All_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod #定义抽象方法,无需实现功能
def read(self):
'子类必须定义读功能'
pass
@abc.abstractmethod #定义抽象方法,无需实现功能
def write(self):
'子类必须定义写功能'
pass
# class Txt(All_file):
# pass
#
# t1=Txt() #报错,子类没有定义抽象方法
class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
抽象类和接口类
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
1.多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口
接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
2.方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
继承的顺序
顺序继承
class A(object):
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性
#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类****
继承的原理的按照:python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表
>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
多态
多态指的是一类事物有多种形态
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao
文件有多种形态:文本文件,可执行文件
import abc
class File(metaclass=abc.ABCMeta): #同一类事物:文件
@abc.abstractmethod
def click(self):
pass
class Text(File): #文件的形态之一:文本文件
def click(self):
print('open file')
class ExeFile(File): #文件的形态之二:可执行文件
def click(self):
print('execute file')
多态性
peo=People()
dog=Dog()
pig=Pig()
#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk()
封装
【封装】
隐藏对象的属性和实现细节,仅对外提供公共访问方式。
【好处】
-
将变化隔离;
-
便于使用;
-
提高复用性;
-
提高安全性;
【封装原则】
-
将不需要对外提供的内容都隐藏起来;
-
把属性都隐藏,提供公共方法对其访问。
私有变量和私有方法
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的
私有变量
#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
class A:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #变形为self._A__X
def __foo(self): #变形为_A__foo
print('from A')
def bar(self):
self.__foo() #只有在类内部才可以通过__foo的形式访问到.
#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
私有的方法:
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
""" 正常的情况 """
class A:
def __init__(self, name, age):
self.name = name
self.age = age
def show(self):
print("name:{name},age:{age}".format(name=self.name, age=self.age))
class B(A):
def __init__(self, name, age, sex):
super().__init__(name, age)
self.sex = sex
def show(self):
print("name:{name},age:{age}:sex:{sex}".format(name=self.name, age = self.age, sex = self.sex))
"""私有的方法定义方式__show()"""
class A:
def __init__(self, name, age):
self.name = name
self.age = age
def __show(self): # 转换为_A__show
print("name:{name},age:{age}".format(name=self.name, age=self.age))
class B(A):
def __init__(self, name, age, sex):
super().__init__(name, age)
self.sex = sex
def __show(self): # 变形为_B__show
print("name:{name},age:{age}:sex:sex}".format(name=self.name, age = self.age, sex = self.sex))
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
return self.__width * self.__length
#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
return self.__width * self.__length * self.__high
property属性
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
class people:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
@property
def bmi(self):
return self.weight /(self.height ** 2)
p = people("Yang", 71, 1.85)
# print(p.bmi()) # 这是没有@property的情况 result: 20.74506939371804
print(p.bmi) # # 这是有@property的情况 result: 20.74506939371804
#注意:
# p.bmi = 12 不能进行赋值 会报错:AttributeError: can't set attribute
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
面向对象的类型有三种类型:
【1】public:这种封装的方式外面是可以访问的
【2】protected:这种封装的方式friend和子类可以访问
【3】privated:这种封装的方式对谁都不能访问
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
@property
def name(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter
def name(self):
raise TypeError('Can not delete')
f=Foo('egon')
print(f.name)
# f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter
def AAA(self,value):
print('set的时候运行我啊')
@AAA.deleter
def AAA(self):
print('delete的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA # get的时候运行我啊
f1.AAA='aaa' #set的时候运行我啊
del f1.AAA # delete的时候运行我啊
classmethod
class Classmethod_Demo():
role = 'dog'
@classmethod
def func(cls):
print(cls.role)
# Classmethod_Demo.func() # 没有@classmethod的时候:TypeError: func() missing 1 required positional argument: 'cls'
Classmethod_Demo.func() # dog
staticmethod
class Staticmethod_Demo():
role = 'dog'
@staticmethod
def func():
print("当普通方法用")
Staticmethod_Demo.func()
面向对象的软件工程包括下面几个部
1.面向对象分析(object oriented analysis ,OOA)
软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2 面向对象设计(object oriented design,OOD)
根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3 面向对象编程(object oriented programming,OOP)
根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4 面向对象测试(object oriented test,OOT)
在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5 面向对象维护(object oriendted soft maintenance,OOSM)
正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
面向对象常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,name__及__doc
摘要的内容来自这里,想查看,请点击这里。
类的高级主题
运算符重载
常见运算符重载方法
方法 | 重载 | 调用 |
---|---|---|
__init__ | 构造函数 | 对象创建: X = Class(args) |
__del__ | 析构函数 | X对象收回 |
__add__ | 运算符+ | 如果没有__iadd__, X+Y, X+=Y |
__or__ | 运算符| | 如果没有__ior__,X|Y, X|=Y |
__repr__, __str__ | 打印,转换 | print(X),repr(X),str(X) |
__call__ | 函数调用 | X(*args, **kwargs) |
__getattr__ | 点号运算 | X.undefined |
__setattr__ | 属性赋值语句 | X.any=value |
__delattr__ | 属性删除 | del X.any |
__getattribute__ | 属性获取 | X.any |
__getitem__ | 索引运算 | X[key],X[i:j] |
__setitem__ | 索引赋值语句 | X[key],X[i:j]=sequence |
__delitem__ | 索引和分片删除 | del X[key],del X[i:j] |
__len__ | 长度 | len(X),如果没有__bool__,真值测试 |
__bool__ | 布尔测试 | bool(X) |
__lt__, __gt__, __le__, __ge__, __eq__, __ne__ | 特定的比较 | X<Y,X>Y,X<=Y,X>=Y, X==Y,X!=Y |
__radd__ | 右侧加法 | other+X |
__iadd__ | 实地(增强的)加法 | X+=Y(or else __add__) |
__iter__, __next__ | 迭代环境 | I=iter(X), next() |
__contains__ | 成员关系测试 | item in X(任何可迭代) |
__index__ | 整数值 | hex(X), bin(X), oct(X) |
__enter__, __exit__ | 环境管理器 | with obj as var: |
__get__, __set__, __delete__ | 描述符属性 | X.attr, X.attr=value, del X.attr |
__new__ | 创建 | 在__init__之前创建对象 |
构造函数和表达式:__int__和__sub__
class Number:
def __init__(self, start): # 实例的构造函数
self.data = start
def __sub__(self, other): # 减法
return Number(self.data - other)
number = Number(10)
Y = number - 2
print(Y.data) # 8
索引和分片:__getitem__和__setitem__
如果类中定义了(或继承)的话,则对应实例索引运算的, 会自动的调用__getitem__实例X出现X[i]这样的索引运算时,Python会自动调用__getitem__方法
class Indexer:
def __getitem__(self, item): # 索引
return item ** 3
index_ = Indexer()
print(index_[2]) # 8
print(index_[5]) # 125
for i in range(10):
print("i:{i}, {i}**{i}".format(i=i), index_[i], end=" ")
print(" ")
class Indexer:
def __init__(self, args):
self.L = args
def __getitem__(self, index):
return self.L[index]
x = Indexer([5, 6, 7, 8])
print(x[0]) # 5
print(x[2]) # 7
class stepper:
data = ""
# def __init__(self, args):
# self.data = args
def __getitem__(self, item): # 列表和元组的赋值运算 会自动调用__getitem__
return self.data[item]
s = stepper()
s.data = "test"
print(s[1])
print("t" in s)
print("a" in s)
print(list(s), tuple(s))
setitem 索引赋值
class Setitemer:
def __init__(self, args):
self.L = args
def __setitem__(self, key, value): # 索引赋值
self.L[key] = value
setitemer = Setitemer([1, 2, 3, 4, 5, 6, 7])
setitemer[1] = 88
print(setitemer.L) # [1, 88, 3, 4, 5, 6, 7]
用户自定义的迭代器
"""
python的迭代环境都会先尝试__iter__方法,在尝试__getitem__,
一般来说,应该优先尝试__iter__ ,它能够比__getitem__更好的支持一般的迭代
"""
class squares:
def __init__(self, start, stop):
self.values = start - 1
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.values == self.stop:
raise StopIteration
self.values += 1
return self.values ** 2
for i in squares(1, 5):
print(i, end=" ")
print(" ")
x = squares(1, 5)
I = iter(x)
有多个迭代器的对象
例如下面是代码:
"""在类中编写多个迭代器的时候,__iter__只需要替换迭代器的定义的新的状态对象,而不是返回self"""
class SkipIterator:
def __init__(self, wrapped):
self.wrapped = wrapped
self.offset = 0
def __next__(self):
if self.offset >= len(self.wrapped):
raise StopIteration
else:
item = self.wrapped[self.offset]
self.offset += 2
return item
class SkipObject:
def __init__(self, wrapped):
self.wrapped = wrapped
def __iter__(self):
return SkipIterator(self.wrapped)
if __name__ == "__main__":
alpha = "abcdef"
skipper = SkipObject(alpha)
I = iter(skipper)
print(next(I), next(I), next(I)) # a c e
for i in skipper:
for i_ in skipper:
print(i+i_, end=" ") # aa ac ae ca cc ce ea ec ee
print(" ")
alpha = "abcdef"
for i in alpha[::2]: # 这个是将所有的值全部加载到内存中
for i_ in alpha[::2]:
print(i+i_, end=" ")
print()
alpha1 = "abcdef"
alpha1 = alpha1[::2] # 只是加载选取的数据:ace到内存中
print(alpha1) # ace
for i in alpha1:
for i_ in alpha1:
print(i+i_, end=" ") # aa ac ae ca cc ce ea ec ee
print(" ")
成员的关系:__contains__、__iter__和__getitem__
在迭代的领域,类通常把in成员关系的运算符实现成一个迭代,使用__iter__方法__getitem__方法,要支持分更加特定的成员的的关系,类编写一个__contains__方法 当出现的时候,该方法优先于__iter__方法优先于__getitem__方法。__contains__方法应该把成员关系定义为对一个映射应用的键(并且可以快速的查找),以及用序列进行搜索。
class Iters:
def __init__(self, values):
self.data = values
def __iter__(self):
print("__iter__")
self.ix = 0
return self
def __next__(self):
print("__next__")
if self.ix == len(self.data):
raise StopIteration
else:
item = self.data[self.ix]
self.ix += 1
return item
def __getitem__(self, item):
print("__getitem__")
return self.data[item]
def __contains__(self, item):
print("__contains__")
return item in self.data
x = Iters([1, 2, 3, 4, 5, 6, 8])
print(3 in x) # True
for i in x:
print(x, end="|")
属性引用:__getattr__和__setattr__
__getattr__方法是拦截属性点号运算,当通过对未定义(不存在)属性名称和实例进行点号运算,就会用属性名称作为字符的调用这个方法,如果python可以通过继承树搜索流程找到这个属性,这个方法就不会被调用,在这种情况下,可通过__getattr__可以作为钩子来通过通用的方式响应属性请求。
class empty:
def __getattr__(self, attrname): #
if attrname == "age":
return 123
else:
raise (AttributeError,attrname)
e = empty()
print(e.age) # e.age会转__getattr_
print(e.name) # AttributeError,name
class accesscontrol():
def __setattr__(self, key, value):
if key == "age":
self.__dict__[key] = value
else:
raise (AttributeError, key +'not allowed')
x = accesscontrol()
x.age = 41 # calls_setattr
print(x.age)
x.name = "Yang"
print(x.name) # AttributeError, name not allowed
__repr__和__str__会返回字符串的表达式
当类的实例打印或转换字符串时__repr__将会被自动调用,可以更好的显示格式,而不是默认的显示格式。
class adder:
def __init__(self, value =0):
self.data = value
def __add__(self, other):
self.data += other
class addrepr(adder):
def __repr__(self):
return "addreper(%s)"%(self.data)
x = addrepr(3) # Runs__init__
x + 1 # Runs__add__
print(x) # Runs__repr__
- 在打印操作会首先尝试__str__和str内置函数,通常会返回一个友好的显示。
- __repr__用于其他的环境中:用于交互模式下提示回应以及repr函数,如果没有使用__str__,会使用print和str,它通常会返回一个编码的字符,可以用来重新创建对象,或者给开发者一个详细显示。
总之:__repr__可以用于任何的地方,除了定义了一个__str__的时候,使用print和str,如果没有定义__str__,打印还是使用__repr__,但是在交互式响应的情况下,只能使用__repr__,不会在尝试__str__。如果想让所有的环境都统一显示,__repr__是最佳的选择。通过定义两种方式:就可以在不同的环境内支持不同的显示。
class addboth(adder):
def __str__(self):
return '[value:%s]'%(self.data)
def __repr__(self):
return "addboth(%s)"%(self.data)
a = addboth(4)
a + 1 # Runs __add__
print(a) # [value:5] # Runs __str__
print(str(a), repr(a)) # [value:5] addboth(5)
为了确保一个定制的显示在所有的环境中都显示而不是容器什么,编写__repr__,而不是__str__,前者什么环境都能显示,而后者如下适应的情况如下:
class Printer:
def __init__(self, value):
self.data = value
def __str__(self):
return str(self.data)
obj = [Printer(1), Printer(12)]
for i in obj:
print(i, end=" ") # 1 12
print(" ")
print(obj) # [<__main__.Printer object at 0x000001DB5EB7B6A0>, <__main__.Printer object at 0x000001DB5EB7B6D8>]
class Printer1:
def __init__(self, value):
self.data = value
def __repr__(self):
return str(self.data)
obj1 = [Printer1(1), Printer1(12)]
for i in obj1:
print(i, end=" ") # 1 12
print(" ")
print(obj1) # [1, 12]
右侧的加法和原处的加法:__radd__和__iadd__
只有当+右侧的对象是类实例,而左边对象不是类的实例时,python才会掉用__radd__,其他的情况由左侧对象调用__add__方法。
class Commuter:
def __init__(self, value):
self.value = value
def __add__(self, other):
print("add", end=" ")
return self.value + other
def __radd__(self, other):
print("Radd", end=" ")
return other + self.value
a = Commuter(10)
print(a + 10) # add 20
print(10 + a) # Radd 20 右侧加法
class Commuter:
def __init__(self, value):
self.data = value
def __add__(self, other):
print("__add__", end=" ")
return Commuter(self.data + other)
def __radd__(self, other):
print("__radd__", end=" ")
if isinstance(other, Commuter): other = other.data
return Commuter(other + self.data)
def __str__(self):
return "<commuter:{0}>".format(self.data)
def __repr__(self):
return "<commuter1:{0}>".format(self.data)
a = Commuter(10)
a + 1
print(a) # __add__ <commuter:10>
1 + a
print(a) # __radd__ <commuter:10>
# 原处的加法:a+=b
class Nunmber:
def __init__(self, value):
self.num = value
def __iadd__(self, other):
print("__iadd__")
self.num += other
return self
number = Nunmber(10)
number += 1
number += 1
print(number.num) # 12
call表达式:__call__
当调用实例,会使用__call__方法,如果python中定义的话。
class Caller:
def __call__(self, *args, **kwargs):
print("__call___", end=" ")
print(args, kwargs)
c = Caller()
print(c(123, 456)) # __call___ (123, 456) {} Runs __call__
print(c(123, 456, yang=456)) # __call___ (123, 456) {'yang': 456} Runs __call__
class Caller:
def __init__(self, value):
self.data = value
def __call__(self, num):
return self.data * num
c = Caller(3)
print(c(10)) # 30
比较:__it__、__gt__和其他的方法
比较是没有右端形式,当一个运算数支持比较的时候,应该使用对应的方法:例如(__it__和__gt__互为对应当方法)
class Test:
def __init__(self, value):
self.data = value
def __gt__(self, other):
return self.data > other
def __lt__(self, other):
return self.data < other
t = Test("a")
print(t > "b") # False
print(t < "b") # True
在python3.x中已将__cop__方法移除了,在python2.x中可以使用。
>>> class test:
... def __init__(self,value):
... self.data = value
... def __cmp__(self,other):
... return cmp(self.data,other)
...
>>> t = test("a")
>>> t > "b"
False
>>> t < "b"
True
布尔:__bool__和__len__
在布尔的环境中python会首先会尝试__bool__来获取一个布尔值,如果没有这个方法,就尝试__len__类根据对象的长度确定一个真值,通常使用对象的状态或其他信息来生成一个布尔的结果。
# bool测试:
class Bool_class:
def __bool__(self):
return True
b = Bool_class()
if b:
print("真的~")
else:
print("假的!!!")
print(bool(b)) # True
# len()测试:
class len_class:
def __len__(self):
return 0
l = len_class()
if l:
print("真的~~")
else:
print("假的!!!")
print(len(l)) # 0
class test:
def __len__(self):
return 0
def __bool__(self): # 会首先调用__bool__,没有这个方法,在调用__len__方法
return True
t = test() # True
if t:
print("Yes!")
'''
result:Yes!
'''
在python2.6中布尔不是使用的是__bool__而是使用的是__nonzero__,python3.0将python2.6的__nonzero__方法重新命为__bool__,python3.0和python2.6都使用了__len__作为候补。
对象析构函数:__del__
每当实例产生时,就会调用__init__构造函数,每当实例空间被收回时(在垃圾收集时),它的对立面__del__,也就是析构函数(destructor method),就会自动执行。
>>> class life:
... def __init__(self, name="unkown"):
... print("hello", name)
... self.name = name
... def __del__(self):
... print("goodbye",self.name)
...
>>> Yang = life("Yang")
hello Yang
>>> Yang = "devil" # Yang 赋值给一个字符串的时候,就会失去最后life的实例,会触发析构函数
>
goodbye Yang