1、Python 面向对象

<1>、面向对象的概念

首先,面向对象并不是哪一门语言的编程,而是一种编程思想,在面向对象的思想里,万物皆对象。面向对象更官方的描述为:

把一组数据结构和处理它们的方法组成对象(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):passdef set_name(self, name):self.name = namedef get_name(self):return self.namedef 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):passdef set_size(self, size):self.size = sizedef get_size(self):return self.sizedef 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() 方法,实现了不同的输出,这就是多态的典型特征。

这里,有必要提及下面向对象的设计原则,有:

  • 单一职责原则 (SRP)- 一个类只做该做的事情(类的设计要高内聚)。
  • 开闭原则 (OCP)- 软件实体应该对扩展开发对修改关闭。
  • 依赖倒转原则(DIP)- 面向抽象编程(在弱类型语言中已经被弱化)。
  • 里氏替换原则(LSP) - 任何时候可以用子类对象替换掉父类对象。
  • 接口隔离原则(ISP)- 接口要小而专不要大而全(Python中没有接口的概念)。
  • 合成聚合复用原则(CARP) - 优先使用强关联关系而不是继承关系复用代码。
  • 最少知识原则(迪米特法则,LoD)- 不要给没有必然联系的对象发消息。

<2>、Python 类的创建,实例化对象

自定义一个汽车类 Car,创建如下:

class Car:carType = ["audiCar", "bwmCar", "greeyCar"]def __init__(self, name, price):self.name = nameself.price = pricedef get_car_infos(self, name=None):if name is not None:self.name = nameprint(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__()就是类的私有方法(即构造方法),不能被该类之外的范围引用。

<3>、Python 类成员(字段,方法,属性)

Python 的字段包括静态字段和普通字段,定义和使用是不一样的,改造下之前的 Car 类:

class Car:#静态字段carType = ["audiCar","bwmCar","greeyCar"]def __init__(self, name, price):#普通字段self.name = "BMW"self.price = price

二者的本质是在内存的保存位置不同,静态字段可理解为类中所有对象共享的变量,而普通字段则是属于具体对象的变量,二者的调用方式也不一样,如下:

# 静态字段,直接使用类名调用
Car.carType # 普通字段,需要实例对象调用
c1.name

Python 的方法,细分的话可分为普通方法、静态方法、类方法,区别如下:

  • 普通方法,默认有个self参数,且只能被对象调用;
  • 静态方法:用 @staticmethod 装饰的不带 self 参数的方法,类的静态方法可以没有参数,可以直接使用类名调用;
  • 类方法,默认有个 cls 参数,需要加上 @classmethod 装饰器,可以被类和对象调用。

举例说明:

class ClassDemo:@staticmethoddef aMethod():print('调用静态方法')@classmethoddef 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@propertydef 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 = namedef __get(self):return self.__namedef __set(self, name):self.__name = namedef __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'

<4>、Python 抽象类

Python 抽象类具有以下特点:

  • Python 本身不具有抽象类,Python3 可通过导入abc模块实现抽象类和抽象方法,而抽象类必须通过ABCMeta元类来定义;
  • 与普通类相比,抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须重写抽象方法;
  • 抽象类,可理解为是一个介于类和接口之间的一个概念,同时具有类和接口的部分特性。

举例说明:

from abc import abstractmethod, ABCMetaclass Base(metaclass=ABCMeta):# 定义抽象方法,无需实现功能@abstractmethoddef method1(self):passdef 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.

<5>、Python 反射机制

所谓的反射,指的是程序运行过程中可以动态(在程序运行时)获取对象的信息。我们知道,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]) 等方法

  • type(obj):判断对象的类型,不会认为子类是一种父类类型,不考虑继承关系;
  • isinstance(obj, classinfo):判断一个对象是否是一个已知的类型,会认为子类是一种父类类型,会考虑继承关系;
  • dir([obj]):获得一个对象的所有属性和方法的列表。
>>> a, b, c, d = 52, 3.14, True, 3+4j# 查询类型
>>> print(type(a), type(b), type(c), type(d))
<class 'int'> <class 'float'> <class 'bool'> <class 'complex'># 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']

2、Python 高级的数据结构

前面已经总结过 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

<1>、namedtuple(命名元组)

顾名思义,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)#<bound method Car._make of <class '__main__.Car'>>
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 更具灵活性,它不仅可以通过 构造器 或 特定方法 创建对象,也可以将元组中元素进行命名,还可以修改元素的值,以及转换成字典对象等。

<2>、deque(双端队列)

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 含义:

  • 添加元素:append、appendleft、extend、extendleft、insert等;
  • 删除元素:pop、popleft 、remove等;
  • 旋转元素rotate、倒序排序reverse 、 统计元素出现次数count 等;
  • 清空队列clear 、复制队列copy 等。

<3>、OrderedDict(有序字典)

见名知意,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')])

总结:

  • 把 od 多插入几个键值对,测试发现输出元素的顺序是按照插入顺序的!
  • 因为 OrderedDict 类是 dict 的子类,OrderedDict 类重写了dict 大部分方法,所以除 API 返回的对象不同外,对象装载的属性内容是相同的。

<4>、ChainMap

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({})

<5>、Counter(计数器)

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 "<stdin>", line 1, in <module>
NameError: name 'count' is not defined

其他方法:清空clear()、复制copy()。

<6>、defaultdict

字典 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 "<stdin>", line 1, in <module>
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(<class 'int'>, {'w': 1, 'x': 2, 5: 1, 2: 1, 1: 1})
>>> print(default['9'])
0

3、Python 迭代器与生成器

在 Python 流程控制语句总结过条件语句,循环语句等基础内容,Python 似乎精简了流程控制语句中的许多东西,使用起来更加的简便,而实际上,for 循环经常还会和迭代器、生成器一起使用,下面总结下迭代器和生成器。

<1>、迭代器

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)
# 结果:<class 'list_iterator'>
print(type(gener))
# 结果:2021 5.2 中国加油 [123, 'abc'] {'key1': True}
for i in gener:print(i, end=' ')
# 结果:<generator object <genexpr> at 0x000001E7A397EF90>
print(i for i in gener)

<2>、生成器

上面迭代器demo里的这段代码,应该注意到了吧:

gener = iter(list)
# 结果:<generator object <genexpr> 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) #<generator object <genexpr> at 0x0000012E5918EF90># 方式二 :斐波那契数列算法
def fibonacci(n,w=0): # 生成器函数 a, b, counter = 0, 1, 0while True:if (counter > n):returnyield aa, b = b, a + bprint('%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
'''

4、Python 函数式编程

之前说明过 Python 的函数概念等基础内容,这里将说明 Python 中的函数闭包、匿名函数、及高阶函数的概念和用法。

<1>、闭包

在 Python 中,函数的返回值类型,既可以是六大基本类型的对象,也可以是面向对象中自定义的对象,而函数返回值是函数类型也是支持的,这就是所谓的闭包。

自定义一个函数outM(),其内部可以再定义一个函数inM(),我们指定outM()返回值类型为inM函数,那么会得到出什么结果呢?测试一下:

从结果可以看出:zz和zz()是两个不同的结果,zz是返回的函数对象,而zz()则是通过调用内部函数,返回的是内部函数返回值,如果变换入参则会输出不同的结果。

这里,有必要理解闭包形成的机制,通俗点理解就是,一个函数内定义了一个内部函数,内部函数对外部函数的作用域变量进行了引用,内部函数就形成了闭包,每次运行程序时,闭包都能记住外部函数的作用域变量值。因此,闭包的一个神奇作用就是可以保持函数状态信息,让作用域的变量值保持上一次的值。闭包的应用案例,例如棋盘游戏,利用闭包特性可以让棋子在棋盘上每次都能从上一步开始移动。

当然,使用闭包也会存在一些陷阱,值得注意:

  • 闭包是无法修改外部函数的作用域变量的;
  • 不能在for循环中使用闭包。

这些陷阱也很好理解,比如,闭包修改了外部函数变量值的话,运行时会发现闭包既要保持外部变量值不变,又要返回修改后的变量值,这明显是矛盾的!for 循环使用闭包也是会出现这个问题!!

<2>、装饰器

提及闭包,就无法绕过 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__ 方法。通过类我们可以将执行过程拆解到各函数中,降低代码的复杂度,甚至可以通过类属性实现一些复杂功能。

<3>、匿名函数

顾名思义,匿名函数就是不再使用 def 这种方式定义函数,而是通过 lambda 关键字创建匿名函数,只有一行代码,紧凑简洁。

语法格式如下:

lambda 参数列表 : 逻辑表达式

举例说明:

#算术运算
lambda a : a+=1
lambda a,b : a*b#转换变量类型
lambda x : str(x)#函数调用,获取返回值
lambda a : func1(a)

当然,使用匿名函数,需要注意:

  • lambda 只能有一个逻辑表达式,且不能使用return,默认返回的是匿名函数的返回值;
  • lambda 虽然大大的简化了代码,但只能有限度的支持逻辑封装;
  • lambda 只能使用自己参数列表的参数(限于自己的命名空间),不能使用外部的参数。

<4>、高阶函数

高阶函数,指的是允许函数作为参数的函数。Python 中支持的高阶函数有:

  1. map(f, list)   ----  将一个list转化成另一个list;
  2. reduce(f, list)--- list中的每个元素反复调用函数f,最终返回结果值;
  3. filter(f, list)   ----   根据函数f逻辑条件进行过滤,返回满足条件的list;
  4. sorted(list)    ----   默认按正序排列list元素;
  5. 自定义排序函数 sorted(list, f)   ----  通过函数f定义排序规则。

结合前面的 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))) 

5、Python 操作数据库

不管 Python 是做数据分析,还是网络爬虫,Web 开发、亦或是机器学习,都离不开要和数据库打交道,而 MySQL 又是最流行的一种数据库,下面以操作 MySQL 数据库为例说明。

首先,考虑的是选择引入的模块,目前有很多模块支持操作 MySQL数据库:

  • MySQL-python:它是许多框架的基础库,但年久失修且安装的前置条件繁多,基本不推荐使用;
  • mysqlclient:是 MySQL-python 的Fork 版本,对于Windows来说不友好,正如简介所说的:“Building mysqlclient on Windows is very hard. But there are some binary wheels you can install easily.”;
  • PyMySQL:纯Python实现的驱动,安装简单,兼容 MySQL-python;
  • SQLAlchemy:一种既支持原生 SQL,又支持 ORM 的工具,相对重量级,安装简单。
  • peewee:最流行的ORM工具之一,属于轻量级,安装简单。

通过依赖查询网址:https://pypi.org/ 查询下载方式及API文档,这里推荐使用PyMySQL、peewee。

<1>、PyMySQL 模块

PyMySQL 是一个使用原生 SQL 操作数据库的第三方模块,首先,下载该模块:

ip install PyMySQL

接着,需要获取数据库连接对象connection,并创建游标cursor对象:

import pymysql,timetry: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 用法如下:

  • cursor.execute(sql, args):执行一次单条sql语句;
  • cursor.executemany(sql, args):执行多次单条sql语句;
  • cursor.rowcount:返回查询结果集总条数;
  • cursor.fetchone():返回查询结果集中的一条记录;
  • cursor.fetchmany(num):返回查询结果集中的指定数量记录;
  • cursor.fetchall():返回查询结果集中的所有记录;
  • cursor.commit():使用游标commit提交更新操作;
  • cursor.rollback():回滚操作;
  • cursor.close():关闭游标。

这里,我们将增删改操作称为更新操作,查询称为查询操作,更新和查询操,演示如下:

#更新操作
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 数据库。

<2>、peewee 模块

ORM,即对象关系映射,核心思路是将实体类与关系型数据库的表一一映射。Python 的第三方模块 SQLAlchemy 和 peewee 都支持ORM,但前者兼容书写原生 SQL,属于重量级的驱动。

这里建议使用轻量级驱动 peewee ,前往pip依赖查询网站查询如何下载该模块,并参考官方 quickstart 提供的 API 文档:http://docs.peewee-orm.com/en/latest/peewee/quickstart.html 。

首先,下载该模块:

pip install peewee

接着,获取数据库连接对象,测试连接下数据库:

import peeweeoptions = {'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_idpublisher_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文档里有很多方法操作表,比如增加/删除表的可选属性:

  • create_table()/create_indexes()/create_sequence()/create_all()/create_foreign_key()等
  • drop_table()/drop_indexes()/drop_sequence()/drop_all()等

<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 项目,根据个人需求选择使用就行。

2、Python 进阶知识总结相关推荐

  1. python进阶之多进程

    python进阶之多进程 0.导语1.进程与线程初识1.1 导包1.2 定义被调函数1.3 创建线程和进程1.4 启动线程和进程2.输出结果存放至Queue2.1 导包2.2 定义被调函数2.3 启动 ...

  2. 1、Python 基础知识总结

    1.Python 简介 Python 是一门简单易学的,面向对象的,解释型的脚本语言,也是一种开源的.可移植性强的.可扩展性强的高级编程语言,常用于科学计算与人工智能.WEB 开发.金融.云计算等领域 ...

  3. 【Python进阶】Python进阶专栏、编程与开源框架知识星球上线,等你来follow

    大家好,今天我将在有三AI开设新专栏<Python进阶>.在这个专栏中,我们会讲述Python的各种进阶操作,包括Python对文件.数据的处理,Python各种好用的库如NumPy.Sc ...

  4. python的格式化输出学号_安利三个关于Python字符串格式化进阶知识

    点击蓝色"Python空间"关注我丫 加个"星标",每天一起快乐的学习 今 日 鸡 汤 名花倾国两相欢,常得君王带笑看. /前言/ 关于Python字符串格式化 ...

  5. python进阶学什么意思_为什么要学习python进阶的知识

    前言 python相对于其它语言来说入门简单.入门后无论是web开发,还是爬虫都可以应付得了.大部分的工作都是在现有的框架下,继承某类,按照要求重写某些方法,然后就可以完成工作了.python进阶的那 ...

  6. 最全面的Python重点知识汇总,建议收藏!

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 来源丨网络,由法纳斯特整理 这是一份来自于 SegmentFault ...

  7. 如果只推荐一本 Python 进阶的书,我要 Pick 它!

    作者 | 豌豆花下猫 今年二月初,我偶然看到了一条推特: <流畅的Python>一书的作者发布了一条激动人心的消息:他正在写作第二版! 如果要票选最佳的 Python 进阶类书目,这本书肯 ...

  8. Python 进阶之路 (九) 再立Flag, 社区最全的itertools深度解析(上)

    前言 大家好,今天想和大家分享一下我的itertools学习体验及心得,itertools是一个Python的自带库,内含多种非常实用的方法,我简单学习了一下,发现可以大大提升工作效率,在sf社区内没 ...

  9. python进阶书籍推荐-豆瓣评分9.4!年度最值得推荐的Python进阶书

    原标题:豆瓣评分9.4!年度最值得推荐的Python进阶书 来自:程序员书库(ID:OpenSourceTop) 编译 链接:https://whatpixel.com/fluent-python-b ...

最新文章

  1. elasticsearch多表关联查询_Mybatis【15】 Mybatis如何实现一对一的情况多表关联查询?...
  2. nova instance出错:message: Proxy error: 502 Read from server failed
  3. java中Map的用法(HaspMap用法)
  4. 关于Unity中Shader的内置值
  5. Asp.Net Core Mvc Razor之RazorPage
  6. java程序弊端_面向对象编程的弊端是什么?
  7. 本地mysql新建用户名和密码错误_mysql新建用户本地无法登录
  8. java编程思想3感悟(4)---被隐藏的具体实现
  9. Mysql权限控制 - 允许用户远程连接
  10. oracle exp执行失败,EXP-00056: 遇到 ORACLE 错误 25153
  11. Open3d之KDTree
  12. 2020年黑苹果硬件配置推荐
  13. 联想计算机怎么开启网络共享,联想笔记本开启Wi-Fi共享图文教程
  14. 《逐梦旅程 WINDOWS游戏编程之从零开始》笔记6——四大变换光照与材质
  15. python二次函数_python数据分析之曲线拟合:二次函数拟合
  16. 电磁学乱七八糟的符号(一)
  17. PL/SQL连接时-Error while trying to retrieve text for error ORA-12154
  18. 计算机考研300分什么水平,工科考研300分什么概念
  19. 3D MAX入门篇(1)常用快捷命令及两种基础建模方式
  20. php显示word,php 浏览word

热门文章

  1. Spark作业调度中stage的划分
  2. ValueError: source code string cannot contain null bytes
  3. PHP intval 与 floatval
  4. “老司机”开车必备贴士
  5. 2019暑假慈溪中学游记
  6. 光影精灵5装双系统遇到的问题
  7. 【山东事业编】公共基础知识备考——中国的地势和地形
  8. 360极速浏览器一键操作 订单速记表selenium +requests爬取 openpyxl +xlrd 操作excel
  9. ArcGIS插件-太乐地图
  10. leaflet加载离线瓦片地图