首先,面向对象并不是哪一门语言的编程,而是一种编程思想,在面向对象的思想里,万物皆对象。面向对象更官方的描述为:
把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。
面向对象描述的很简洁,把类与对象的关系说的也很明白,当然我们也可以从以下角度理解:
面向对象描述的也很高级,重要的是凸显了面向对象的几个特征:
1.1、抽象
类本身就是抽象的概念,抽象正式定义的话,可以描述为:把一组对象共同的特征提取出来组成类的过程,特征包括属性特征和行为特征。
举例,有一组不同品牌的汽车对象,属性和行为特征描述如下:
audiCar: 属性特征有 —— audi A6L、四轮驱动、65L油箱、360全景影像、6年10w公里保养、油耗小等;行为特征有 —— 提速快(百公里加速只需8s)、无自适应巡航等。
bmwCar:属性特征有 —— BMW X3、炭黑色车身、75L油箱、售价389800RMB、6年20w公里保养、油耗略高等;行为特征有 —— 提速快(百公里加速只需6s)、自适应巡航、车道自动跟车等。
greeyCar:属性特征有 —— 博越 2020pro、后轮驱动、60L油箱、6年15w公里保养、油耗略高等;行为特征有 —— 快(百公里加速只需6s)、自适应巡航等。
提取这些对象的共同特征: 属性特征 —— 品牌名称、油箱容量、保养期限、油耗;行为特征 —— 提速、巡航方式。提取这些对象的共同特征和行为特征的过程就是抽象了。
1.2、封装
封装其实就是,隐藏了对象特征实现的细节,只向外界提供一系列的接口 API 来操作该对象。
Python 中的模块最能体现封装的概念了。比如,Python 的内置 time 模块,它内部封装了对时间字符串、时间戳、时间元组等时间类型操作的 API,使用时引入该模块就可以访问它提供的功能了,而不需要关注它实现的具体细节。
又比如,自定义一个 Walk 类,如下:
# 定义一个会行走的动物的类
class Walk():
def __init__(self):
pass
def set_name(self, name):
self.name = name
def get_name(self):
return self.name
def walking(self):
print("hello, my name is {}".format(self.name))
那么,使用 w = Walk() 获取了类的实例 w,如果我操作 name = "Trump" 的数据的设置与读取,则可以调用 w 的 set_name()、get_name() 等方法操作,因为这几个方法封装在了 Walk 类。
1.3、继承
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
我们知道 Java 是单继承的,而 Python 是支持多继承的,Python 多继承的用法如下:
#父类1:接上面的 Walk 类
# 父类2:会飞翔的动物
class Fly():
def __init__(self):
pass
def set_size(self, size):
self.size = size
def get_size(self):
return self.size
def flying(self):
print("hello, I can flying")
# 子类1:会捉老鼠的猫(是会行走的动物)
class Cat(Walk):
def catchMice(self):
print("I can catch mice!")
def walking(self):
print("I can also walking, I am ketty!")
# 子类2:很会抓猎物的老鹰(是会行走也会飞翔的动物)
class Eagle(Walk, Fly):
def catchAnything(self):
print("I can also catch anything!")
def flying(self):
print("hello, I can also flying")
def walking(self):
print("I can also walking")
注意:
1.4、多态
多态指的是允许不同子类型的对象对同一消息做出不同的响应,简而言之,用同样的对象引用调用同样的方法但做了不同的事情,多态的前提是继承。
仍拿上面的代码为例,子类 Cat 和 Eagle 都继承了父类 Walk,并且都重写了父类的 walking() 方法,实现了不同的输出,这就是多态的典型特征。
这里,有必要提及下面向对象的设计原则,有:
自定义一个汽车类 Car,创建如下:
class Car:
carType = ["audiCar", "bwmCar", "greeyCar"]
def __init__(self, name, price):
self.name = name
self.price = price
def get_car_infos(self, name=None):
if name is not None:
self.name = name
print(self.name, " select cars have:", Car.carType)
注意,__init__是一个特殊方法,用于在创建对象时进行初始化操作,也就是说,可以通过这个构造方法实例化对象,如下:
# 创建了一个450000元BWM车的对象 c1
c1 = Car("BWM", 450000)
# 创建了一个140000元GREEY车的对象 c2
c2 = Car("GREEY", 140000)
如果访问对象的属性或方法,使用 实例名.属性 、实例名.方法名 方式:
print(c1.name)
print(c1.price)
c1.get_car_infos("weixiangxiang")
这里顺带说下访问权限吧,与 Java 使用指定的修饰符(public、protected、private)控制字段和方法的访问范围有所不同,Python 没有修饰符的概念,而是使用__xx__形式表示私有的字段或方法,使用_xx_形式表示受保护的字段或方法,而不带_的则表示公有的字段或方法,比如__init__()就是类的私有方法(即构造方法),不能被该类之外的范围引用。
Python 的字段包括静态字段和普通字段,定义和使用是不一样的,改造下之前的 Car 类:
class Car:
#静态字段
carType = ["audiCar","bwmCar","greeyCar"]
def __init__(self, name, price):
#普通字段
self.name = "BMW"
self.price = price
二者的本质是在内存的保存位置不同,静态字段可理解为类中所有对象共享的变量,而普通字段则是属于具体对象的变量,二者的调用方式也不一样,如下:
# 静态字段,直接使用类名调用
Car.carType
# 普通字段,需要实例对象调用
c1.name
Python 的方法,细分的话可分为普通方法、静态方法、类方法,区别如下:
举例说明:
class ClassDemo:
@staticmethod
def aMethod():
print('调用静态方法')
@classmethod
def bMethod(cls):
print('调用类方法')
# 普通方法
def cMethod(self):
print('调用普通方法')
调用方式:
# 直接使用类名调用
ClassDemo.aMethod()
ClassDemo.bMethod()
#使用实例对象调用
demo = ClassDemo()
demo.aMethod()
demo.bMethod()
demo.cMethod()
Python 的方法属性,使用@porperty装饰器装饰方法,程序中可以把函数“当作”属性访问,从而提供更加友好的访问方式,可以用来控制可读、可写、可删除等操作。
举例说明:
class Demo1:
def __init__(self, name):
self.__name = name
@property
def name(self): # 只读,无法修改和删除
return self.__name
测试如下:(允许读,但不允许修改和删除)
>>> t = Demo1('weixiangxiang')
>>> t.name
weixiangxiang
>>> t.name = "zzzzzzz"
AttributeError: can't set attribute
>>> del t.name
AttributeError: can't delete attribute
又如:
class Demo2:
def __init__(self, name):
self.__name = name
def __get(self):
return self.__name
def __set(self, name):
self.__name = name
def __del(self):
del self.__name
# 可读可写可删除
name = property(__get, __set, __del)
测试如下:(允许读取,修改和删除)
>>> t = Demo2('weixiangxiang')
>>> t.name
weixiangxiang
>>> t.name = "zzzzzzz"
>>> t.name
zzzzzzz
>>> del t.name
>>> t.name
AttributeError: 'Demo2' object has no attribute '_Demo2__name'
Python 抽象类具有以下特点:
举例说明:
from abc import abstractmethod, ABCMeta
class Base(metaclass=ABCMeta):
# 定义抽象方法,无需实现功能
@abstractmethod
def method1(self):
pass
def method2(self):
print('This is Base method2.')
class children(Base):
# 子类继承抽象类,必须重写抽象方法
def method1(self):
print('This is children method1.')
测试如下:
if __name__ == '__main__':
child = children()
child.method1() # This is children method1.
child.method2() # This is Base method2.
所谓的反射,指的是程序运行过程中可以动态(在程序运行时)获取对象的信息。我们知道,Python 是一门动态解释型语言,只有在程序运行时才能知道数据的类型及对象包含的属性和方法等信息。
Python 提供了几个内置方法,getattr()、hasattr()、setattr()、delattr(),通过字符串来操作属性值,如下:
hasattr()# 判断对象是否存在
print(hasattr(obj,'name'))
getattr()# 获取属性
print(getattr(obj,'name'))
setattr()# 为属性赋值
print(setattr(obj,'name','egon'))
print(obj.name)
delattr()# 删除属性
delattr(obj,'name')
这里,顺带介绍下 type(obj)、isinstance(obj, )、dir([obj]) 等方法
>>> a, b, c, d = 52, 3.14, True, 3+4j
# 查询类型
>>> print(type(a), type(b), type(c), type(d))
# a是否存在元组内
>>> isinstance(a,(int,float,bool))
True
# 查看set集合对象的信息
>>> dir(set())
['__and__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
前面已经总结过 Python 的六大基本数据类型,随着 Python 学习的深入,会遇到一些复杂的处理场景,此时这些基本数据类型就显得不再够用了。好在 Python 提供了一个内置的 collections 模块,这个模块实现了专门的容器数据类型,提供 Python 通用的内置容器 dict、list、set 和 tuple 的替代方案。
打开Lib\collections下的__init__.py 脚本,通过源码看一下有哪些特别的数据结构,文件开头对它们也做了功能说明:
* namedtuple:factory function for creating tuple subclasses with named fields
* deque:list-like container with fast appends and pops on either end
* ChainMap:dict-like class for creating a single view of multiple mappings
* Counter:dict subclass for counting hashable objects
* OrderedDict:dict subclass that remembers the order entries were added
* defaultdict:dict subclass that calls a factory function to supply missing values
* UserDict:wrapper around dictionary objects for easier dict subclassing
* UserList:wrapper around list objects for easier list subclassing
* UserString:wrapper around string objects for easier string subclassing
这里,特别需要关注的数据结构有:namedtuple、deque、Counter、ChainMap、defaultdict、OrderedDict 等,我们可以通过下面方式引入:
from collections import namedtuple
from collections import deque
from collections import Counter
from collections import ChainMap
from collections import defaultdict
from collections import OrderedDict
顾名思义,namedtuple 是一个具有属性命名的元组结构,那么它与 tuple 有哪些区别呢,通过对比说明下:
>> 定义、索引元素的方式不同:
#创建方式1:()
t1 = (315, 3.14, True, [23, 22], ('hello',), {'k1':False})
#创建方式2:tuple()
t2 = tuple(range(1, 10))
#通过下标索引元素
print(t1[0])
print(t2[0])
#通过namedtuple构造器创建对象Car
Car = namedtuple('Car', ['id', 'name', 'type', 'speed'])
#设置对象Car属性方式1:构造器
#car = Car(id=1001, name="吉利博越", type="2018款", speed=78.5)
#设置对象Car属性方式2:_make()
car = Car._make([1001, "吉利博越", "2018款", 78.5])
#通过属性名获取元素
print(str(car.id) + str(car.name) + str(car.type) + str(car.speed))
通过源码中的 class_namespace 看一下 namedtuple 的其他方法:
class_namespace = {
'__doc__': f'{typename}({arg_list})',
'__slots__': (),
'_fields': field_names,
'_field_defaults': field_defaults,
'__new__': __new__,
'_make': _make,
'_replace': _replace,
'__repr__': __repr__,
'_asdict': _asdict,
'__getnewargs__': __getnewargs__,
}
注意:在 Python 中用前缀下划线标识方法/属性的访问权限,比如,_xx_相当于 Java 中的protected、__xxx__相当于Java中的 private。
测试一下可访问的方法,结果如下:
#('id', 'name', 'type', 'speed')
print(car._fields)
#{}
print(car._field_defaults)
#>
print(car._make)
#Car(id=1001, name='吉利博越', type='2018款', speed=90.4)
print(car._replace(speed=90.4))
#{'id': 1001, 'name': '吉利博越', 'type': '2018款', 'speed': 78.5}
print(car._asdict())
总结:与 不可变的 tuple 相比,namedtuple 更具灵活性,它不仅可以通过 构造器 或 特定方法 创建对象,也可以将元组中元素进行命名,还可以修改元素的值,以及转换成字典对象等。
deque 是一个基于列表而实现的双端队列结构,具有栈(FILO)和队列(FIFO)的特性。我们可以通过 deque() 创建一个无固定长度的队列,也可以指定长度而创建定长的队列,如下:
>>> from collections import deque
>>>
>>> q = deque()
>>> print(q) #deque([])
>>> q1 = deque(maxlen=5)
>>> print(q1) #deque([], maxlen=5)
>>> q2 = deque(range(1,7),maxlen=4)
>>> print(q2) #deque([3, 4, 5, 6], maxlen=4)
通过 dir 可以查看 deque() 具有的 API 方法:
>>> print(dir(deque()))
['__add__', '__bool__', '__class__', '__class_getitem__', '__contains__'
, '__copy__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__'
, '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__'
, '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__'
, '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__'
, '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__'
, '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__'
, '__subclasshook__', 'append', 'appendleft', 'clear', 'copy', 'count'
, 'extend', 'extendleft', 'index', 'insert', 'maxlen', 'pop', 'popleft'
, 'remove', 'reverse', 'rotate']
可以看到,deque作为双端队列,默认是在右端操作的,如:append、extend、pop,也可以左端操作:appendleft、extendleft、popleft 。API 含义:
见名知意,OrderedDict 类是一个按元素输入顺序进行输出的字典结构,类似 Java 中 LinkedHashMap。OrderedDict 使用示例如下:
from collections import OrderedDict
#创建dict
od = {'name':'wxx', 'pos':2, 'result':[range(5)]}
#创建orderedDict
d = OrderedDict(od)
#修改元素
d['pos']=6
#判断key是否在字典里
print('pos' in d) #True
#获取所有的key
print(d.keys()) #odict_keys(['name', 'pos', 'result'])
#根据key获取value
print(d['result']) #[range(0,5)]
#获取所有的value
print(d.values()) #odict_values(['wxx', 6, [range(0, 5)]])
#key不存在则设置key及默认的value,返回value
print(d.setdefault('pos',6)) #6
#根据key删除value
print(d.pop('result')) #[range(0, 5)]
#获取所有的键值对
print(d.items()) #odict_items([('name', 'wxx'), ('pos', 6)])])
#随机删除字典的最后一个键值对
print(d.popitem()) #('pos', 6)
print(d.items()) #odict_items([('name', 'wxx')])])
#copy
d1 = d.copy()
print(d1) #OrderedDict([('name', 'wxx')])
#clear
d1.clear()
print(d1) #OrderedDict()
#创建新的字典,无初始值默认None
d2 = d.fromkeys(['aaa', 'bbbb', 100])
print(d2) #OrderedDict([('aaa', None), ('bbbb', None), (100, None)])
d3 = d.fromkeys(['aaa', 'bbbb', 100], 'hello')
print(d3) #OrderedDict([('aaa', 'hello'), ('bbbb', 'hello'), (100, 'hello')])
总结:
ChainMap 是将多个字典合并成一个字典并放到一个列表中的数据结构,ChainMap 中每次加入新的字典也不会产生新的对象,类似 Java 中 StringBuffer。使用示例如下:
>>> from collections import ChainMap
>>>
>>> d1 = {'k1':2, 'k2':'nice'} #字典1
>>> d2 = {'g7':7, 'k2':'good'} #字典2
>>> d = ChainMap(d1, d2) #合并字典1和字典2
>>> print(d.maps)
[{'k1': 2, 'k2': 'nice'}, {'g7': 7, 'k2': 'good'}]
对于多个字典键重复的问题,ChainMap 会取第一次出现在字典里的键值对,后面字典不会再出现,遍历合并后的字典可以看到,如下:
>>> for item in d.items():
... print(item)
...
('g7', 7)
('k2', 'nice')
('k1', 2)
当然,可以继续追加新的字典:
>>> d3 = {'wecode':'666','lol':3.14}
>>> d = d.new_child(d3)
>>> print(d)
ChainMap({'wecode': '666', 'lol': 3.14}, {'k1': 2, 'k2': 'nice'}, {'g7': 7, 'k2': 'good'})
从上面的结果来看,ChainMap 还有另一层含义,就是将字典是按顺序连接起来的。
通过 dir(d) 查询下 ChainMap 还有哪些方法,如下:
>>> print(dir(d))
['_MutableMapping__marker', '__abstractmethods__', '__bool__',
'__class__', '__contains__', '__copy__', '__delattr__',
'__delitem__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__getitem__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__',
'__le__', '__len__', '__lt__', '__missing__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__reversed__', '__setattr__', '__setitem__', '__sizeof__',
'__slots__', '__str__', '__subclasshook__', '__weakref__',
'_abc_impl', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys',
'maps', 'new_child', 'parents', 'pop', 'popitem', 'setdefault',
'update', 'values']
ChainMap 继承了 dict 类,重写了 dict 许多方法。这里需要注意属性 parents,它也说明了ChainMap 是按顺序连接字典的,如下:
>>> d.parents
ChainMap({'k1': 2, 'k2': 'nice'}, {'g7': 7, 'k2': 'good'})
>>> d.parents.parents
ChainMap({'g7': 7, 'k2': 'good'})
>>> d.parents.parents.parents
ChainMap({})
Counter是一个基于字典结构的用于跟踪和统计元素出现次数的计数器,通过Counter(iter)创建计数器对象,入参为可迭代的数据类型,如字符串、元组、列表、字典和集合等,也可以从字典对象创建,及从一组键值对创建。
>>> from collections import Counter
>>> #入参iter为可迭代的数据类型
>>> print(Counter('helloworld'))
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, 'w': 1, 'r': 1, 'd': 1})
>>> print(Counter((True,3.14,3.15,True,False)))
Counter({True: 2, 3.14: 1, 3.15: 1, False: 1})
>>> print(Counter(['W','x','x','好人','是','好人']))
Counter({'x': 2, '好人': 2, 'W': 1, '是': 1})
>>> print(Counter(set(range(5))))
Counter({0: 1, 1: 1, 2: 1, 3: 1, 4: 1})
>>> #入参iter为可迭代的数据类型
>>> print(Counter({'wxx':2, '28':1}))
Counter({'wxx': 2, '28': 1})
>>> #从一组键值对创建
>>> print(Counter(x=5,y=3))
Counter({'x': 5, 'y': 3})
Counter 继承了 dict 类,可以使用 dict 类的方法,而 Counter 支持的 API 有哪些呢?查询如下:
>>> print(dir(Counter()))
['__add__', '__and__', '__class__', '__contains__', '__delattr__',
'__delitem__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__getitem__',
'__gt__', '__hash__', '__iadd__', '__iand__', '__init__',
'__init_subclass__', '__ior__', '__isub__', '__iter__', '__le__',
'__len__', '__lt__', '__missing__', '__module__', '__ne__',
'__neg__', '__new__', '__or__', '__pos__', '__reduce__',
'__reduce_ex__', '__repr__', '__reversed__', '__setattr__',
'__setitem__', '__sizeof__', '__str__', '__sub__',
'__subclasshook__', '__weakref__', '_keep_positive',
'clear', 'copy', 'elements', 'fromkeys', 'get', 'items', 'keys',
'most_common', 'pop', 'popitem', 'setdefault', 'subtract',
'update', 'values']
统计元素出现的次数:下标方式&get()方式
#下标方式
>>> count = Counter('helloworld')
>>> print(count['o']) #统计o字符出现的次数
2
>>> print(count['y']) #统计不存在的字符出现的次数
0
#get()方式
>>> print(count.get('h'))
1
>>> print(count.get('l'))
3
>>> print(count.get('o'))
2
统计重复元素:elements()
>>> print(tuple(count.elements()))
('h', 'e', 'l', 'l', 'l', 'o', 'o', 'w', 'r', 'd')
>>> print(list(count.elements()))
['h', 'e', 'l', 'l', 'l', 'o', 'o', 'w', 'r', 'd']
统计TopN元素:most_common()
>>> print(count.most_common()) #所有元素,按出现次数从大到小排序
[('l', 3), ('o', 2), ('h', 1), ('e', 1), ('w', 1), ('r', 1), ('d', 1)]
>>> print(count.most_common(2)) #top2,出现次数2次及以上的
[('l', 3), ('o', 2)]
统计所有的键:keys()
>>> print(count.keys())
dict_keys(['h', 'e', 'l', 'o', 'w', 'r', 'd'])
统计所有的值:values()
>>> print(count.values())
dict_values([1, 1, 3, 2, 1, 1, 1])
统计所有的键值对:items()
>>> print(count.items())
dict_items([('h', 1), ('e', 1), ('l', 3), ('o', 2), ('w', 1), ('r', 1), ('d', 1)])
更新(增加元素:update() ,减少元素:subtract()):
>>> print(count.update('lol')) #加入2个l字符,1个o字符
None
>>> print(count.most_common(2)) #top2,出现次数2次及以上的
[('l', 5), ('o', 3)]
>>> print(count.subtract('lol')) #减少2个l字符,1个o字符
None
>>> print(count.most_common(2))
[('l', 3), ('o', 2)]
>>>
删除元素:pop()、popitem()、del
>>> print(count.pop('h')) #删除字符h,返回value
1
>>> print(count.most_common())
[('l', 3), ('o', 2), ('e', 1), ('w', 1), ('r', 1), ('d', 1)]
>>> print(count.popitem()) #随机删除最后一个元素
('d', 1)
>>> print(count.most_common())
[('l', 3), ('o', 2), ('e', 1), ('w', 1), ('r', 1)]
>>> del count #删除整个Counter对象
>>> print(count.most_common())
Traceback (most recent call last):
File "", line 1, in
NameError: name 'count' is not defined
其他方法:清空clear()、复制copy()。
字典 dict 的用法在前面已经梳理过,简单的讲就是,通过{}创建字典,通过下标或者 get() 访问字典等,然而通过下标或者 get() 访问字典不存在的键时,下标方式访问会报错,如下:
>>> dt = {'h':1, 'e':2, 'k':1}
>>> print(dt['h'])
1
>>> print(dt.get('h'))
1
>>> print(dt['m'])
Traceback (most recent call last):
File "", line 1, in
KeyError: 'm'
>>> print(dt.get('m'))
None
为了解决这个报错问题,collections 模块提供了 defaultdict,它是 dict 类的子类,可以通过接受一个指定参数类型,来初始化字典,即使不存在查询的键也不会报错,如下:
>>> from collections import defaultdict
>>>
>>> default = defaultdict(int)
>>>
>>> for item in ['w','x','x',5,2,1]:
... default[item] += 1
...
>>> print(default)
defaultdict(, {'w': 1, 'x': 2, 5: 1, 2: 1, 1: 1})
>>> print(default['9'])
0
在 Python 流程控制语句总结过条件语句,循环语句等基础内容,Python 似乎精简了流程控制语句中的许多东西,使用起来更加的简便,而实际上,for 循环经常还会和迭代器、生成器一起使用,下面总结下迭代器和生成器。
Python中的迭代器是访问集合元素的一种方式,通过迭代器提供的函数iter() 和 next()可以很方便的遍历访问每一个元素,比如字符串,元组以及列表都可用于创建迭代器。
使用举例:
# 使用元组创建迭代器
tup = (2021, 5.20, '中国加油', [123,'abc'], {"key1":True})
iters = iter(tup)
print(next(iters)) #2021
print(next(iters)) #5.2
# 使用列表创建迭代器
list = [2021, 5.20, '中国加油', [123,'abc'], {"key1":True}]
gener = iter(list)
# 结果:
print(type(gener))
# 结果:2021 5.2 中国加油 [123, 'abc'] {'key1': True}
for i in gener:
print(i, end=' ')
# 结果: at 0x000001E7A397EF90>
print(i for i in gener)
上面迭代器demo里的这段代码,应该注意到了吧:
gener = iter(list)
# 结果: at 0x000001E7A397EF90>
print(i for i in gener)
没错,这里出于好奇,我原本想打印出迭代器输出的每个元素看看,结果返回给我一个generator, 这就是生成器。按照生成器定义描述的话,就是一边循环一边计算的机制称为生成器,为什么会有生成器的出现呢?
我们都知道,定义列表的元素都是存放在内存中的,数据量不大时不会有什么影响。但是,当海量的列表数据需要处理时,往往只需要处理列表的某一段数据,其余的则会浪费以及消耗宝贵的内存资源,解决这种问题就需要生成器 generator。
生成器的创建方式大致有两种,一种是将列表的[]换成()即可,另一种是使用yield关键字(yield表示暂停并返回当前yield的值,下一次调用执行next()时会从yield位置开始继续运行,这样不用寻找所有的元素,对内存很友好,一般列表数据量很大且需要动态计算时才使用yield)创建生成器。
举例说明:
# 方式一
list = [2021, 5.20, '中国加油', [123,'abc'], {"key1":True}]
g1 = (i for i in list)
print(g1) # at 0x0000012E5918EF90>
# 方式二 :斐波那契数列算法
def fibonacci(n,w=0): # 生成器函数
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a
a, b = b, a + b
print('%d,%d' % (a,b))
counter += 1
f = fibonacci(10,0) # f 是一个迭代器,由生成器返回生成
while True:
try:
print (next(f), end=" ")
except :
sys.exit()
'''
0 1,1
1 1,2
1 2,3
2 3,5
3 5,8
5 8,13
8 13,21
13 21,34
21 34,55
34 55,89
55 89,144
'''
之前说明过 Python 的函数概念等基础内容,这里将说明 Python 中的函数闭包、匿名函数、及高阶函数的概念和用法。
在 Python 中,函数的返回值类型,既可以是六大基本类型的对象,也可以是面向对象中自定义的对象,而函数返回值是函数类型也是支持的,这就是所谓的闭包。
自定义一个函数outM(),其内部可以再定义一个函数inM(),我们指定outM()返回值类型为inM函数,那么会得到出什么结果呢?测试一下:
从结果可以看出:zz和zz()是两个不同的结果,zz是返回的函数对象,而zz()则是通过调用内部函数,返回的是内部函数返回值,如果变换入参则会输出不同的结果。
这里,有必要理解闭包形成的机制,通俗点理解就是,一个函数内定义了一个内部函数,内部函数对外部函数的作用域变量进行了引用,内部函数就形成了闭包,每次运行程序时,闭包都能记住外部函数的作用域变量值。因此,闭包的一个神奇作用就是可以保持函数状态信息,让作用域的变量值保持上一次的值。闭包的应用案例,例如棋盘游戏,利用闭包特性可以让棋子在棋盘上每次都能从上一步开始移动。
当然,使用闭包也会存在一些陷阱,值得注意:
这些陷阱也很好理解,比如,闭包修改了外部函数变量值的话,运行时会发现闭包既要保持外部变量值不变,又要返回修改后的变量值,这明显是矛盾的!for 循环使用闭包也是会出现这个问题!!
提及闭包,就无法绕过 Python 高级特性里的装饰器,装饰器的基础就是闭包,有必要掌握。装饰器本质也是一个函数闭包,上面的函数 outM() 就是一个装饰器,它在保证内部函数 inM() 具有内部函数的功能外,可以在外部函数中额外添加其他的功能,也就是功能增强的意思。因此,装饰器可以用于打印日志、性能测试、事务处理、缓存、权限校验等场景。
原理说起来很简单,但具体怎么使用装饰器呢?Python 支持用注解的方式,将需要使用装饰器的地方加上@即可。
举例说明,自定义装饰器:(注意,无参形式的话直接使用@outM即可)
#带参数的装饰器
@outM(ls=[1,2,3,4])
def f1()
print('---------')
@outM(ls=[5,7.0,[1,4],4])
def f2()
print('---------')
从面向对象角度看,Python 提供了@staticmathod、@classmethod、@property等用在方法上的装饰器,用于标识该方法的特性。
当然,类也能实现装饰器的功能,是由于当我们调用一个对象时,实际上调用的是它的 __call__
方法。通过类我们可以将执行过程拆解到各函数中,降低代码的复杂度,甚至可以通过类属性实现一些复杂功能。
顾名思义,匿名函数就是不再使用 def 这种方式定义函数,而是通过 lambda 关键字创建匿名函数,只有一行代码,紧凑简洁。
语法格式如下:
lambda 参数列表 : 逻辑表达式
举例说明:
#算术运算
lambda a : a+=1
lambda a,b : a*b
#转换变量类型
lambda x : str(x)
#函数调用,获取返回值
lambda a : func1(a)
当然,使用匿名函数,需要注意:
高阶函数,指的是允许函数作为参数的函数。Python 中支持的高阶函数有:
结合前面的 lambda 表达式,举例说明高阶函数用法:
#自定义函数f1()
def f1(x):
return x*3
#使用高阶函数
print map(f1,range(1,6)) #将list每个元素扩大三倍
# 匿名表达式简化map()、reduce()、filter():
print map(lambda x:x*3 , range(1,6)) #[3, 6, 9, 12, 15]
print reduce(lambda x,y:x*y , range(1,6)) #120
#[0,1,2,3,4,5]
print filter(lambda z:z and len(z.strip())>0 , str(range(0,6)))
不管 Python 是做数据分析,还是网络爬虫,Web 开发、亦或是机器学习,都离不开要和数据库打交道,而 MySQL 又是最流行的一种数据库,下面以操作 MySQL 数据库为例说明。
首先,考虑的是选择引入的模块,目前有很多模块支持操作 MySQL数据库:
通过依赖查询网址:https://pypi.org/ 查询下载方式及API文档,这里推荐使用PyMySQL、peewee。
PyMySQL 是一个使用原生 SQL 操作数据库的第三方模块,首先,下载该模块:
ip install PyMySQL
接着,需要获取数据库连接对象connection,并创建游标cursor对象:
import pymysql,time
try:
print('正在连接数据库......')
connection = pymysql.connect(host='localhost',
port=3307,
db='taobao_test',
user='root',
密码='')
print('数据库连接成功!!!')
cursor = connection.cursor()
print('创建游标成功!!!')
except Expection as e:
raise e
然后,使用游标 cursor 执行 SQL,相关的操作 API 用法如下:
这里,我们将增删改操作称为更新操作,查询称为查询操作,更新和查询操,演示如下:
#更新操作
try:
insert_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime());
insert_sql = 'INSERT INTO taobao_test.order VALUES(1005,'+insert_time+',"懒人烤肉饭套餐(大份)",null,null,null,null,null,null,null,null,null,null,null);'
rs = cursor.execute(insert_sql)
if rs == 1:
print('数据insert成功!!!')
connection.commit()
update_sql = 'UPDATE taobao_test.order SET order_name="懒人烤肉饭套餐(超大份)",order_type=1 WHERE order_id=1003;'
result = cursor.execute(update_sql)
if result == 1:
print('数据update成功!!!')
connection.commit()
delete_sql = 'delete from taobao_test.order where order_id=1000;'
res = cursor.execute(delete_sql)
if res == 1:
print('数据delete成功!!!')
connection.commit()
except Exception as e:
con.rollback() #失败需要回滚
raise e
#查询操作
try:
query_alltable_sql = 'SHOW TABLES;'
cursor.execute(query_alltable_sql)
print('查询数据库所有的表:\n' + str(cursor.fetchall()))
query_onetable_sql = 'SELECT * FROM taobao_test.order;'
cursor.execute(query_onetable_sql)
#print('查询order表一条的结果集:' + str(cursor.fetchone()))
#print('查询order表多条的结果集:' + str(cursor.fetchmany(2)))
print('查询order表所有的结果集:\n' + str(cursor.fetchall()))
except Exception as e:
raise e
finally:
cursor.close()
connection.close()
print('已释放所有资源!!!')
测试结果:
执行更新操作前的数据库表情况:
执行更新操作后的数据库表情况:
上面使用原生的 SQL 对数据库表进行了增删改查等常见操作,然而,对于大型应用项目而言,基本上会使用 ORM 框架来操作 MySQL 数据库。
ORM,即对象关系映射,核心思路是将实体类与关系型数据库的表一一映射。Python 的第三方模块 SQLAlchemy 和 peewee 都支持ORM,但前者兼容书写原生 SQL,属于重量级的驱动。
这里建议使用轻量级驱动 peewee ,前往pip依赖查询网站查询如何下载该模块,并参考官方 quickstart 提供的 API 文档:http://docs.peewee-orm.com/en/latest/peewee/quickstart.html 。
首先,下载该模块:
pip install peewee
接着,获取数据库连接对象,测试连接下数据库:
import peewee
options = {'host':'', 'port':3307, 'user':'root', '密码':''}
try:
db = peewee.MySQLDatabase('taobao_test', **options)
if db.connect():
print('数据库连接成功!!!')
except Exception as e:
raise e
既然是 ORM 工具,我们需要考虑类与表的对应关系,如果数据库中该表不存在的话,我们肯定需要先去创建,peewee 是如何操作的呢?
假设需要定义一个 Book 实体类,它含有 book_id(int),book_name(string),publisher(object),book_price(float),publish_time(date) 等字段,其中 publisher 表示作者信息,也是一个对象类型,它有 publisher_id(int),publisher_name(string),publisher_mail(string) 等字段。
实体类与表的对应关系,如下所示:
Object | Corresponds to… |
---|---|
Model class | Database table |
Field instance | Column on a table |
Model instance | Row in a database table |
同时,需要注意不同数据库字段类型是不同的:
Field Type | Sqlite | Postgresql | MySQL |
---|---|---|---|
AutoField |
integer | serial | integer |
BigAutoField |
integer | bigserial | bigint |
IntegerField |
integer | integer | integer |
BigIntegerField |
integer | bigint | bigint |
SmallIntegerField |
integer | smallint | smallint |
IdentityField |
not supported | int identity | not supported |
FloatField |
real | real | real |
DoubleField |
real | double precision | double precision |
DecimalField |
decimal | numeric | numeric |
CharField |
varchar | varchar | varchar |
FixedCharField |
char | char | char |
TextField |
text | text | text |
BlobField |
blob | bytea | blob |
BitField |
integer | bigint | bigint |
BigBitField |
blob | bytea | blob |
UUIDField |
text | uuid | varchar(40) |
BinaryUUIDField |
blob | bytea | varbinary(16) |
DateTimeField |
datetime | timestamp | datetime |
DateField |
date | date | date |
TimeField |
time | time | time |
TimestampField |
integer | integer | integer |
IPField |
integer | bigint | bigint |
BooleanField |
integer | boolean | bool |
BareField |
untyped | not supported | not supported |
ForeignKeyField |
integer | integer | integer |
这里,先定义这两个实体类,并设置对应表的属性、别名,并创建表:
# 重构,Book类与Publisher类都继承BaseModel父类
class BaseModel(Model):
class Meta:
database = db;
class Book(Model):
book_id = IntegerField(primary_key=True, null=False, index=True)
book_name = CharField(max_length=100,null=False)
#作者id可以约定为:名称小写+出生年月+book_id
publisher_id = CharField(null=False)
book_price = FloatField(null=False)
publish_time = DateField()
class Meta:
#默认映射的表名为book,也可以自定义表名 或 别名
#table_name = 'bookinfo'
table_alias = 'bookinfo'
class Publisher(Model):
publisher_id = CharField(primary_key=True, null=False)
publisher_name = CharField(null=False)
publisher_mail = CharField()
ForeignKeyField(Book, backref='weixiangxiang—19930307-xxx')
class Meta:
#默认映射的表名为publisher,也可以自定义表名 或 别名
#table_name = 'publisherinfo'
table_alias = 'publisherinfo'
#建表的两种方式:类名.create_table()或db.create_tables([X1, X2,...])
Book.create_table()
Publisher.create_table()
#db.create_tables([Book, Publisher])
Book 类和 Publisher 类的各属性函数中可以设置映射到数据库表中的属性,梳理总结下这些属性的设置选项:
名称 | 描述 | 举例 |
primary_key | 是否为主键 | primary_key=True |
null | 是否为空,默认True | null=False |
index | 是否唯一索引,默认False | index=True |
unique | 是否约束索引,默认False | unique=True |
default | 默认值,默认None | default='localhost' |
column_name | 列名称设置 | column_name='价格' |
max_length | 字段最大长度设置 | max_length=100 |
verbose_name | 别名 | verbose_name=’书价‘ |
backref | 用于子类向父类中反查 | backref='罗生门' |
related_name |
用于子类向父类中反查,有related_name用related_name反查,没有使用backref反查 | related_name='罗生门' |
动态增加属性:
peewee 通过_meta属性支持属性的动态添加的,演示如下:
#Book类不添加任何属性字段
class Book(Model):
#book_id = IntegerField(primary_key=True)
#book_name = CharField(max_length=100)
#作者id可以约定为:名称小写+出生年月+book_id
#publisher_id = CharField(null=False)
#book_price = FloatField(null=False)
#publish_time = DateField()
class Meta:
#默认映射的表名为book,也可以自定义表名 或 别名
#table_name = 'bookinfo'
table_alias = 'bookinfo'
def save_book():
Book._mate.add_field('book_id', IntegerField(primary_key=True)
Book._mate.add_field('book_name', CharField(max_length=100)
Book._mate.add_field('publisher_id', CharField(null=False))
Book._mate.add_field('book_price', FloatField(null=False))
Book._mate.add_field('publish_time', DateField())
print('Book属性设置OK')
CRUD操作:
<1>、API文档里有很多方法操作表,比如增加/删除表的可选属性:
<2>、表中插入数据的API也有很多:
插入一条记录:
# 方式一:使用类的实例,调用构造器 或者 set方法
book = Book(book_id=1001, publisher_id='jclzj-1951-1001',
book_name='罗生门', book_price=58.5)
book.save()
bk = Book()
bk.book_id=1002
bk.book_name='木偶人'
bk.save()
# 方式二:调用create()
Book.create(book_id=1001, publisher_id='jclzj-1951-1001',
book_name='罗生门', book_price=58.5)
# 方式二:调用insert().execute()
Book.insert(book_id=1001, publisher_id='jclzj-1951-1001',
book_name='罗生门', book_price=58.5).execute()
批量插入:
# 方式一:调用create(),支持多参
datas= [
{'book_id': 1001, 'publisher_id': 'jclzj-1951-1001',},
{'book_id': 1002, 'publisher_id': 'dygw-1978-1002',}
]
for data in datas:
Book.create(**data)
# 方式二:调用insertmany().execute()
Book.insertmany(datas).execute()
# 方式三:开启事务,进行原子性操作
with db.atomic():
for data in datas:
Book.create(**data)
with db.atomic():
Book.insertmany(data).execute()
插入前判断记录是否存在(对无主键的表较为适用)
Book.get_or_create(book_id=1002, defaults={'book_name':'春风十里'})
<3>、表中更新/查询操作:
这里就不再赘述了,使用详情可以参考 API 文档:
http://docs.peewee-orm.com/en/latest/peewee/querying.html# 。
总结:Python 操作 MySQL 数据库,其中 PyMySQL 模块适合执行sql脚本时使用,而 peewee 模块更适合用于 Web 项目,根据个人需求选择使用就行。