1. 迭代器(iterator)

  • 迭代是访问集合元素的一种方式。
  • 迭代器是一个可以记住遍历的位置的对象。
  • 迭代器对象从集合的第一个元素开始访问,只能往前不能后退。
  • 实现了iter方法和next方法的对象,就是迭代器。

2. 可迭代对象(iterable)

  • 可以通过 for…in… 这类语句迭代读取的数据对象为可迭代对象。
  • 具备iter方法的对象,就是一个可迭代对象。

常见可迭代对象数据类型:

  • 列表(list)
  • 元祖(tuple)
  • 字符串(str)
  • 字典(dict)
  • 集合(set)

判断一个对象是否是可迭代对象:

  1. from collections import Iterable # 判断迭代器则导入Iterator
  2. isinstance('abc', Iterable) # True
  3. isinstance({}, Iterable) # True
  4. isinstance(123, Iterable) # False

for item in Iterable循环的本质:

先通过iter()函数获取可迭代对象iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

3. 生成器(generator)

  • 迭代器需要自己记录当前迭代的状态,才能根据当前状态生成下一个数据。
  • 生成器能记录当前状态,并配合next()函数进行迭代使用,语法简便。
  • 生成器是一类特殊的迭代器。
  • 使用了yield的函数不再是函数,而是生成器。

创建生成器:

(1)将列表生成式的 [] 改成 ()

  1. In [15]: L = [ x*2 for x in range(5)]
  2. In [16]: L
  3. Out[16]: [0, 2, 4, 6, 8]
  4. In [17]: G = ( x*2 for x in range(5))
  5. In [18]: G
  6. Out[18]: <generator object <genexpr> at 0x7f626c132db0>

(2)使用yield关键字的函数

斐波那契数列(迭代器版):

  1. class FibIterator(object):
  2. """斐波那契数列迭代器"""
  3. def __init__(self, n):
  4. """
  5. :param n: int, 指明生成数列的前n个数
  6. """
  7. self.n = n
  8. # current用来保存当前生成到数列中的第几个数了
  9. self.current = 0
  10. # num1用来保存前前一个数,初始值为数列中的第一个数0
  11. self.num1 = 0
  12. # num2用来保存前一个数,初始值为数列中的第二个数1
  13. self.num2 = 1
  14. def __next__(self):
  15. """被next()函数调用来获取下一个数"""
  16. if self.current < self.n:
  17. num = self.num1
  18. self.num1, self.num2 = self.num2, self.num1 + self.num2
  19. self.current += 1
  20. return num
  21. else:
  22. raise StopIteration
  23. def __iter__(self):
  24. """迭代器的__iter__返回自身即可"""
  25. return self
  26. if __name__ == '__main__':
  27. fb = FibIterator(10)
  28. for i in fb:
  29. print(i, end=' ') # 0 1 1 2 3 5 8 13 21 34

斐波那契数列(生成器版):

  1. def fib(n):
  2. current = 0
  3. num1, num2 = 0, 1
  4. while current < n:
  5. num = num1
  6. num1, num2 = num2, num1 + num2
  7. current += 1
  8. yield num
  9. return
  10. F = fib(10)
  11. 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. 闭包

  1. 在函数内部再定义一个函数,并且这个函数用到了外层函数的变量,那么将这个函数以及用到的一些变量称之为闭包。
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. 可变类型与不可变类型

可变类型(内容可变,地址不变):

列表(list)、字典(dict)、集合(set)。

不可变类型(内容一变,地址就变):

整型(int)、浮点型(float)、字符串(string)、元祖(tuple)。

9. 浅拷贝与深拷贝与=赋值

  • 赋值(=)是进行对象引用(内存地址)的传递。
  • 浅拷贝(x.copy()或copy.copy(x))只拷贝对象本身,不会拷贝起内部的嵌套对象。对于内部的嵌套对象,依然使用原始的引用(顶层拷贝)。

浅拷贝可以分两种情况讨论:

  1. 当拷贝的只是不可变对象(数字、字符串、元祖)时和“赋值”的情况一样,是对象引用(同一内存地址)。
  2. 当拷贝的值是可变对象(列表、集合、字典)时会产生一个“不是那么独立的对象”存在。又为分两种情况:
    1. 复制的对象中无嵌套复杂对象
    2. 复制的对象中有嵌套复杂对象(例如一个列表中的一个子元素是一个列表)时,两个子元素列表是指向同一内存地址的。
    • 深拷贝(copy.deepcopy(x))是对于一个对象所有层次的拷贝(递归拷贝,新建内存空间)。

赋值与深浅拷贝.png

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 中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象。