1. 迭代器(iterator)
- 迭代是访问集合元素的一种方式。
- 迭代器是一个可以记住遍历的位置的对象。
- 迭代器对象从集合的第一个元素开始访问,只能往前不能后退。
- 实现了iter方法和next方法的对象,就是迭代器。
2. 可迭代对象(iterable)
- 可以通过 for…in… 这类语句迭代读取的数据对象为可迭代对象。
- 具备iter方法的对象,就是一个可迭代对象。
常见可迭代对象数据类型:
- 列表(list)
- 元祖(tuple)
- 字符串(str)
- 字典(dict)
- 集合(set)
判断一个对象是否是可迭代对象:
from collections import Iterable # 判断迭代器则导入Iterator
isinstance('abc', Iterable) # True
isinstance({}, Iterable) # True
isinstance(123, Iterable) # False
for item in Iterable循环的本质:
先通过iter()函数获取可迭代对象iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
3. 生成器(generator)
- 迭代器需要自己记录当前迭代的状态,才能根据当前状态生成下一个数据。
- 生成器能记录当前状态,并配合next()函数进行迭代使用,语法简便。
- 生成器是一类特殊的迭代器。
- 使用了yield的函数不再是函数,而是生成器。
创建生成器:
(1)将列表生成式的 [] 改成 ()
In [15]: L = [ x*2 for x in range(5)]
In [16]: L
Out[16]: [0, 2, 4, 6, 8]
In [17]: G = ( x*2 for x in range(5))
In [18]: G
Out[18]: <generator object <genexpr> at 0x7f626c132db0>
(2)使用yield关键字的函数
斐波那契数列(迭代器版):
class FibIterator(object):
"""斐波那契数列迭代器"""
def __init__(self, n):
"""
:param n: int, 指明生成数列的前n个数
"""
self.n = n
# current用来保存当前生成到数列中的第几个数了
self.current = 0
# num1用来保存前前一个数,初始值为数列中的第一个数0
self.num1 = 0
# num2用来保存前一个数,初始值为数列中的第二个数1
self.num2 = 1
def __next__(self):
"""被next()函数调用来获取下一个数"""
if self.current < self.n:
num = self.num1
self.num1, self.num2 = self.num2, self.num1 + self.num2
self.current += 1
return num
else:
raise StopIteration
def __iter__(self):
"""迭代器的__iter__返回自身即可"""
return self
if __name__ == '__main__':
fb = FibIterator(10)
for i in fb:
print(i, end=' ') # 0 1 1 2 3 5 8 13 21 34
斐波那契数列(生成器版):
def fib(n):
current = 0
num1, num2 = 0, 1
while current < n:
num = num1
num1, num2 = num2, num1 + num2
current += 1
yield num
return
F = fib(10)
print(list(F)) # [0 1 1 2 3 5 8 13 21 34]
yield关键字的作用:
- 保存当前运行状态(断点),然后暂停执行,将生成器函数挂起。
- 将yield关键字后面表达式的值作为返回至返回,相当于return作用。
Python3中的生成器可以return一个值,Python2中生成器的return后面不能有任何表达式。
唤醒生成器函数:
- next()
- next()
- send(data)
4. 闭包
在函数内部再定义一个函数,并且这个函数用到了外层函数的变量,那么将这个函数以及用到的一些变量称之为闭包。
def line_conf(a, b):
def line(x):
return a*x + b
return line
line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5))
print(line2(5))
闭包的特点:
- 减少了参数的传递,优化了变量,增加可移植性和复用性,原来需要类对象完成的工作,闭包也可以完成。
- 由于闭包引用了外部函数的局部变量,导致外部函数的局部变量没有及时释放,消耗内存。
修改外层函数中的变量(nonlocal):
nonlocal需要绑定一个局部变量
def counter(start=0):
def incr():
nonlocal start
start += 1
return start
return incr
c1 = counter(5)
print(c1()) # 6
print(c1()) # 7
5. 变量作用域
global关键字用来在函数或其他局部作用域中使用全局变量。但是如果不修改全局变量也可以不使用global关键字。
声明全局变量,如果在局部要对全局变量修改,需要在局部也要先声明该全局变量:
gcount = 0
def global_test():
global gcount
gcount +=1
print (gcount)
global_test() # 1
在局部如果不声明全局变量,并且不修改全局变量。则可以正常使用全局变量:
gcount = 0
def global_test():
print(gcount)
global_test() # 0
global 定义的变量,表明其作用域在局部以外,即局部函数执行完之后,不销毁 函数内部以global定义的变量:
def add_a():
global a
a = 3
add_a()
print(a) # 3
nonlocal关键字用来在函数或其他作用域中使用外层(非全局)变量:
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
mc = make_counter()
print(mc()) # 1
print(mc()) # 2
print(mc()) # 3
def scope_test():
def do_local():
# 此函数定义了另外的一个spam字符串变量,
# 并且生命周期只在此函数内。此处的spam和外层的spam是两个变量,
# 如果写出spam = spam + “local spam” 会报错
spam = "local spam"
def do_nonlocal():
nonlocal spam # 使用外层的spam变量
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignmane:", spam)
do_nonlocal()
print("After nonlocal assignment:",spam)
do_global()
print("After global assignment:",spam)
scope_test()
print("In global scope:",spam)
outputs:
After local assignmane: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
6. 装饰器
在不改变原来函数或类代码的基础上,为已经存在的对象添加额外的功能。
简单函数装饰器:
def use_logging(func):
def wrapper():
print("%s is running" % func.__name__)
return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
return wrapper
def foo():
print('i am foo')
# 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,
# 这条语句相当于 foo = wrapper
foo = use_logging(foo)
foo() # 执行foo()就相当于执行 wrapper()
@语法糖:
def use_logging(func):
def wrapper():
print("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo() # 使用@语法糖,可以省去foo = use_logging(foo)
被装饰的函数有参数:
def timefun(func):
def wrappedfunc(*args, **kwargs): # 使用*args和**kwargs来接收不定长参数
func(*args, **kwargs)
return wrappedfunc
@timefun
def foo(a, b, c):
print(a+b+c)
foo(3,5,7) # 15
带参数的装饰器:
使用带参数的装饰器时,外层要多包裹一层函数用于接收装饰器的参数。
def use_logging(level): # 接收装饰器的参数
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
print("[WARN]%s is running" % func.__name__)
elif level == "info":
print("[INFO]%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo() # [WARN]foo is running
# i am foo
类装饰器:
相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。
使用类装饰器主要依靠类的call方法,当使用@形式将类装饰器附加到函数上时,就会调用此方法。
class Foo(object):
def __init__(self, func): # 接收函数
# 为了能够在__call__方法中调用原来bar指向的函数
# 所以在__init__方法中就需要一个实例属性来保存这个函数体的引用
self._func = func
def __call__(self): # 调用方法
print('class decorator runing')
self._func()
print('class decorator ending')
@Foo # 类名装饰
def bar():
print('bar')
bar() # class decorator runing
# bar
# class decorator ending
多个装饰器的执行顺序
@a
@b
@c
def f ():
pass
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于:
f = a(b(c(f)))
使用装饰器会造成函数的元信息丢失
def note(func):
"""note function"""
def wrapper():
"""wrapper function"""
print('note something')
return func()
return wrapper
@note
def test():
"""test function"""
print('I am test')
test()
# 输出test函数的文档注释
print(test.__doc__) # note something
# I am test
# wrapper function
结果输出的是wrapper函数的文档注释。
Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用。
from functools import wraps # 从functools中导入wraps装饰器
def note(func):
"""note function"""
@wraps(func) # 给wrapper函数加上wraps装饰器,带上参数func
def wrapper():
"""wrapper function"""
print('note something')
return func()
return wrapper
@note
def test():
"""test function"""
print('I am test')
test()
print(test.__doc__) # note something
# I am test
# test function
7. is与==
- is是比较两个引用是否指向了同一个对象(引用比较)。
- ==是比较两个对象的值是否相等(值比较)。
- ==实质上是调用对象的eq方法。
8. 可变类型与不可变类型
可变类型(内容可变,地址不变):
不可变类型(内容一变,地址就变):
整型(int)、浮点型(float)、字符串(string)、元祖(tuple)。
9. 浅拷贝与深拷贝与=赋值
- 赋值(=)是进行对象引用(内存地址)的传递。
- 浅拷贝(x.copy()或copy.copy(x))只拷贝对象本身,不会拷贝起内部的嵌套对象。对于内部的嵌套对象,依然使用原始的引用(顶层拷贝)。
浅拷贝可以分两种情况讨论:
- 当拷贝的只是不可变对象(数字、字符串、元祖)时和“赋值”的情况一样,是对象引用(同一内存地址)。
- 当拷贝的值是可变对象(列表、集合、字典)时会产生一个“不是那么独立的对象”存在。又为分两种情况:
- 复制的对象中无嵌套复杂对象
- 复制的对象中有嵌套复杂对象(例如一个列表中的一个子元素是一个列表)时,两个子元素列表是指向同一内存地址的。
- 深拷贝(copy.deepcopy(x))是对于一个对象所有层次的拷贝(递归拷贝,新建内存空间)。
10. 私有化变量
- xx: 公有变量
- _x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问。
- __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)。
- xx:双前后下划线,用户名字空间的魔法对象或属性。例如:init ,不要自己发明这样的名字。
- xx_:单后置下划线,用于避免与Python关键词的冲突。
当类设置了私有变量后,因为外部不能直接访问,要为其额外增加方法来访问和修改私有变量(使用@property装饰器):
class Money(object):
def __init__(self):
self.__money = 0
@property
# 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用此下一行的方法
def money(self):
return self.__money
@money.setter
# 使用装饰器对money进行装饰,当对money设置值时,调用下一行的方法
def money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
a = Money()
a.money = 100 # 使用@property装饰器后,与公有变量操作无异
print(a.money)
11.面向对象 / 类(OOP)
类属性与实例属性
- 实例属性属于各个实例所有,互不干扰;
- 类属性属于类所有,所有实例共享一个属性;
- 实例对象无法通过直接引用来修改类属性,直接引用修改会新生成一个同名的实例属性,并会屏蔽同名类属性。
```python
class People(object):
address = ‘山东’ # 类属性
def init(self):
self.name = '小王' # 实例属性 self.age = 20 # 实例属性
p = People() p.age = 12 # 修改实例属性 print(p.name) # 小王 print(p.age) # 12
print(p.address) # 访问类属性—>山东 p.address= ‘北京’ # 这里修改的不是类属性,而是给p新建了一个实例属性address print(p.address) # 访问的是新建的实例属性—>北京
print(People.address) # 访问类属性,类属性并未被修改—>山东
print(People.name) # 错误,类不能访问实例对象的属性 print(People.age) # 错误
<a name="g0XhX"></a>
#### 静态方法和类方法(@staticmethod & @classmethod)
- **类方法**使用 **@classmethod** 装饰器,可以使用类(也可使用实例)来调用方法(**带参数cls,表示类本身**)。
- **静态方法**使用 **@staticmethod **装饰器,它是跟类有关系但在运行时又不需要实例和类参与的方法(**没有 self 和 cls 参数**),可以使用类和实例来调用。
在静态方法中引用类属性的话,必须通过类对象来引用:
```python
class People(object):
country = 'china'
@staticmethod
def getCountry(): # 静态方法不需要参数
return People.country # 通过类对象来引用
p = People()
print(p.getCountry()) # china
print(People.getCountry()) # china
使用类方法,实例对象可以对类属性进行修改:
class People(object):
country = 'china'
@classmethod
def getCountry(cls): # 参数cls就是People类本身
return cls.country
@classmethod
def setCountry(cls,country):
cls.country = country
p = People()
print(p.getCountry()) # china
print(People.getCountry()) # china
p.setCountry('japan') # 实例对象调用类方法,修改类属性country
print(p.getCountry()) # japan
print(People.getCountry()) # japan
多态:
定义时的类型和运行时的类型不一样,此时就成为多态。
多继承中同名函数的调用优先级(mro):
class base(object):
def test(self):
print('----base test----')
class A(base):
def test(self):
print('----A test----')
# 定义一个父类
class B(base):
def test(self):
print('----B test----')
# 定义一个子类,继承自A、B
class C(A,B):
pass
obj_C = C()
obj_C.test() # ----A test----
print(C.__mro__) # 可以查看C类的对象搜索方法时的先后顺序
# 输出:(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.base'>, <class 'object'>)
# 优先级:C > A > B > base
重写与调用父类方法:
子类中重写同名方法会覆盖掉父类方法:
class Animal(object):
def sayHello(self):
print("---1---")
class Cat(Animal):
def sayHello(self):
print("---2---")
kitten= Cat()
kitten.sayHello() # ---2---
调用父类方法:
class Animal(object):
def __init__(self,name):
self.name = name
self.color = 'yellow'
class Cat(Animal):
def __init__(self,name):
# 调用父类的__init__方法1(python2)
#Cat.__init__(self,name)
# 调用父类的__init__方法2
#super(Bosi,self).__init__(name)
# 调用父类的__init__方法3
super().__init__(name)
def getName(self):
return self.name
kitten= Cat('mimi')
print(kitten.name) # mimi
print(kitten.color) # yellow
魔法方法 / 属性:
- new 在 init 之前被调用,用来创建实例。
- del:删除一个对象时调用,析构函数。
- doc:查看类文档注释。
- str 是用 print 和 str 显示的结果,repr 是直接显示的结果。
slots:类属性,限制该class实例能添加的属性,不能限制继承它的子类。 ```python class Person(object): slots = (“name”, “age”) # 限制为只能添加name和age实例属性
def init(self):
self.name = '老宋' self.age = 22 # self.score = 90 # 不可定义该属性
P = Person() P.name = “老王” P.age = 20 print(P.name) print(P.age)
P.score = 100 # 报错:AttributError: ‘Person’ object has no attribute ‘score’ print(P.score) ```
- call 使得可以对实例进行调用,类装饰器。
- mro:查看类的对象搜索方法时的先后顺序。
- getitem 用类似 obj[key] 的方式对对象进行取值。
- getattr 用于获取不存在的属性 obj.attr。
12. 垃圾回收机制
python采用的是引用计数机制为主,分代收集机制为辅的策略。
- 小整数[-5,256]共用对象,常驻内存。
- 单个字符共用对象,常驻内存。
- 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁。
- 字符串(含有空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁。
- 大整数不共用内存,引用计数为0,销毁。
- 数值类型和字符串类型在 Python 中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象。