Python面向对象、魔法方法

写在篇前

  OOP(Object Oriented Programming),即面向对象的程序设计,不同于传统的面向过程的程序设计,它大大地降低了软件开发的难度,使编程就像搭积木一样简单,是当今编程以及模式设计一股势不可挡的潮流。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。面向对象编程的基础就是类,所谓“类生一,一生二,二生三,三生万物”,类是对现实世界事物的抽象,一个类包括了现实世界中一组对象的方法和属性。

封装

  封装就是把数据和操作数据的方法绑定起来,通过已定义的接口实现对数据的访问以及修改,屏蔽繁杂的技术细节。编写一个类就是对数据和数据操作的封装,可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。如下示例代码,即是封装了一个Student类,包含类属性Student_number,对象属性__idnamescore,以及一个打印学生成绩的方法print_info(),这样实例化对象之后没就可以直接调用print_info()方法获取学生信息,而无需关注其实现细节。

#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'

class Student(object):
    """
    Student class
    """

    # student_number是一个类属性, 可通过类访问,也可通过对象访问,所有对象共享该表量
    student_number = 0

    # 方法可有默认参数、可变参数、关键字参数和命名关键字参数
    def __init__(self, sid, name, score):
        """
        Student class init method
        """
        Student.student_number += 1
        self.__id = sid
        self.name = name
        self.score = score

    def print_info(self):
        """
        get Student info
        """
        print('%s: %s' % (self.name, self.score), end=' ')
        
bart = Student('001', 'Bart Simpson', 59)
bart.print_score()
print(bart.student_number)  # 1
print(Student.student_number)  # 1

lisa = Student('002', 'Lisa Simpson', 87)
lisa.print_score()
print(bart.student_number)  # 2
print(Student.student_number)  # 2

继承

  继承是从已有类得到继承信息创建新类的过程,一方面可以继承父类所有的属性和方法,另一方面可以增加或则重写父类方法,以适应具体业务流程。

单继承

 这里紧接上面的例子,创建一个MasterStudent类,使其继承Student类,这样子类并拥有了父类的所有方法和属性(实际上父类__init__()方法不会被继承,这一点放到后面再讨论);另外再增加新的属性major和新的方法change_major()

class MasterStudent(Student):
    """
    MasterStudent class
    """
    def __init__(self, sid, name, score, major):
        """
        Student class init method
        """
        super().__init__(sid, name, score)
        self.major = major

    def print_info(self):
        """
        get Student score
        """
        super().print_info()  # 调用父类方法
        print('and majoring in %s' % self.major)

    def change_major(self, major):
        """
        change major
        """
        self.major = major


if __name__ == '__main__':
    ms = MasterStudent('001', 'jeffery', '90', 'bio')
    ms.print_info()

  关于继承,需要注意的一个问题是构造方法(__new__)和初始化方法(__init__)的执行顺序:

class A(object):
    def __new__(cls, *args, **kwargs):
        print('A __new__')
        return super().__new__(cls, *args, **kwargs)

    def __init__(self):
        print('A __init__')


class B(A):
    def __new__(cls, *args, **kwargs):
        print('B __new__')
        return super().__new__(cls, *args, **kwargs)

    def __init__(self):
        print('B __init__')
        super().__init__()
# 输出结果
B __new__
A __new__
B __init__
A __init__

Mixin

 Mixin是python中多继承的一种方案,但是事实上它和一般意义上的多继承(其他编程语言如C++)是有区别的,它更像是Java中的接口,但是是提供默认实现的接口(Java接口默认不提供实现)。举个例子民航飞机是一种交通工具,对于土豪们来说直升机也是一种交通工具。对于这两种交通工具,它们都有一个功能是飞行,但是轿车没有。所以,我们不可能将飞行功能写在交通工具这个父类中。但是如果民航飞机和直升机都各自写自己的飞行方法,又违背了代码尽可能重用的原则(如果以后飞行工具越来越多,那会出现许多重复代码)。怎么办,那就只好让这两种飞机同时继承交通工具以及飞行器两个父类,这样就出现了多重继承。这时又违背了继承必须是”is-a”关系。这个难题该怎么破?

 下面,我们用python实现一下:

class Transportation(object):
    """
    交通工具的基类
    """
    pass


class FlyMixin(object):
    """
    Mixin 类,告诉其他人,这是一个"接口",能够扩充类的某一个功能
    """
    def fly_able(self):
        pass


class Airliner(Transportation, FlyMixin):
    pass


class Helicopter(Transportation, FlyMixin):
    pass

 Mixin其实应用非常广泛,在django中,如某一个页面需要登陆才允许访问,则可以让相应View继承LoginRequiredMixin

class LogoutView(LoginRequiredMixin, View):
    """
    比如退出登陆,当然禁止没有登陆的用户执行该操作
    """
    def get(self, request):
        logout(request)
        return HttpResponseRedirect(reverse("index"))

 看完代码,你会很明显的发现FlyMixin不就是一个普通类吗?所谓Mixin不就是多继承吗?对,但是我们一般约定以Mixin结尾的类作为接口类,用来灵活扩充类的功能。

抽象

 抽象类是一个特殊的类,它从一堆类中抽取相同的内容,组建成一个新的类。它的特殊之处在于只能被继承,不能被实例化。比如三角形类、正方形类、菱形类都是属于图形类,都应该有边长属性,求面积、求周长的方法。下面举一个实例,说明python中抽象类的使用。

class A(object):
    """
    抽象类不能被实例化
    """
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def load(self, _input):
        pass

    @abc.abstractmethod
    def save(self, output, data):
        pass


class B(A):
    def load(self, _input):
        return _input.read()

    def save(self, output, data):
        return output.write(data)


if __name__ == '__main__':
    print(issubclass(B, A))
    print(isinstance(B(), A))
    print(A.__subclasses__())

 还有一种通过__subclasshook__魔法方法的用法我在这也提一下:

class C(object, metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def say(self):
        pass

    @classmethod
    def __subclasshook__(cls, _cls):
        """
        该方法意味着只要一个类实现了他的抽象方法,就会被认为是该类的子类
        """
        print('class invoke ********')
        if cls is C:
            if any("say" in B.__dict__ for B in _cls.__mro__):
                return True
        return NotImplemented


class D(object):
    def say(self):
        print('function say implemented in subclass')


print(issubclass(D, C))
print(isinstance(D(), C))
print(D.__dict__)
print(C.__subclasshook__(D))

多态

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态,多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

class Car:
    def __init__(self, name):
        self.name = name

    def drive(self):
        raise NotImplementedError("Subclass must implement abstract method")

    def stop(self):
        raise NotImplementedError("Subclass must implement abstract method")


class SportsCar(Car):
    def drive(self):
        return 'SportsCar driving!'

    def stop(self):
        return 'SportsCar breaking!'


class Truck(Car):
    def drive(self):
        return 'Truck driving slowly because heavily loaded.'

    def stop(self):
        return 'Truck breaking!'


cars = [Truck('Bananatruck'),
        Truck('Orangetruck'),
        SportsCar('Z3')]

for car in cars:
    print(car.name + ': ' + car.drive())

 但是,如果你仔细看完上面的代码,似乎你觉得并没有什么特殊之处。因为,Python 没有覆写(override)的概念,也就是说严格来讲,Python 并不支持多态。相反,个人觉得,上述代码表现的行为,用带太语言编程里的鸭子类型也许更合适。

 在动态语言中经常提到鸭子类型,所谓鸭子类型就是:If it walks like a duck and quacks like a duck, it must be a duck鸭子类型是编程语言中动态类型语言中的一种设计风格,一个对象的特征不是由父类决定,而是通过对象的方法决定的。

特殊方法&属性

  python类中有很多特殊方法、属性或则约定,熟悉这些特性,能让你编写更加健壮的代码,主要如下:

  • __XXX

 该类变量是一个私有变量(private),只有内部可以访问,外部不能访问。

  • _XXX_

 该类变量是特殊变量,特殊变量是可以直接访问的,不是private。
变量。

  • _XXX

 该类变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思
就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

  • XXX_

    定义的一个变量和某个保留关键字冲突,这时候可以使用单下划线作为后缀, 以示区分。

特殊属性

# 用类名和对象名效果会不同,请自己尝试

MasterStudent(obj).__doc__ # 类注释
MasterStudent(obj).__name__ # 类名(可能带命名空间)
MasterStudent(obj).__bases__ # 直接父类
MasterStudent(obj).__dict__  # 类信息,字典类型
MasterStudent(obj).__class__ # 一般实例调用__class__属性时会指向该实例对应的类,然后可以再去调用其它类属性(注意是类属性,不是实例属性)
MasterStudent(obj).__module__  # 模块名
MasterStudent(obj).__mro__  # 多重继承init时的解析顺序,(<class '__main__.MasterStudent'>, <class '__main__.Student'>, <class 'object'>)
MasterStudent(obj).__qualname__  # qualified refernece name,见PEP3155
MasterStudent(obj).__slots__  # 见下面解释
MasterStudent(obj).method.__annotations__  # 注解,见下面解释
  • __slot__

     用于限制类中的属性(只能是这些属性),使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。如果父类与子类中都定义了__slot__,则结果为父类与子类__slot__的合集。

import enforce

class Student(object):
     __slots__ = ('name', 'score')
     @enforce.runtime_validation
     def __init__(self, name: str):  # annotations
         self.name = name
  • __annotations__

    __annotations__是python3引入的类型检查机制,是一种推荐性写法,如果要强制runtime进行类型检查,可以使用enforce包,并用装饰器@enforce.runtime_validation装饰相应的方法。查看annotations使用MasterStudent.__init__.__annotations__

魔法方法

  • __init__(),__del__(),__new__()

     这个方法是一个类的构造函数,与之对应的__del__()是析构函数,通过此方法我们可以定义一个对象的初始操作。但实际上,新式类的__new__()才是真正的初始化函数。

     首先,我们验证一下这三个方法的执行顺序:

    class A(object):
        def __init__(self):
            print('__init__')
    
        def __new__(cls, *args, **kwargs):
            print('__new__')
            return super().__new__(cls, *args, **kwargs)  # cls表示一个类,一个当前要被实例化的类,参数由py解释器自动提供
    
        def __del__(self):
            print('__del__')
    
    
    a = A()
    print('do something')
    # __new__
    # __init__
    # do something
    # __del__
    
    # 实际上,__new__()负责创建一个对象,__init__负责定制化该对象,即是在对象创建好之后初始化变量
    

     既然知道了__new__()方法,我们是不是可以考虑一下,如何应用它呢?最常见的就是单例模式了,下面给出实现实例。

    class Singleton(object):
        _instance = None
    
        def __new__(cls, *args, **kwargs):
            """
            注意这实际上是一个类方法, cls 表示当前类
            :param args:
            :param kwargs:
            :return:
            """
            if cls._instance is None:
                cls._instance = super().__new__(cls, *args, **kwargs)
    
            return cls._instance
    
    
    s1 = Singleton()
    s2 = Singleton()
    if s1 is s2:
        print('yeah')
    
  • __repr__(),__str__()

     分别为str()、repr()函数提供接口,打印的更好看。

    class Pair:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def __repr__(self):
            return 'Pair({0.x!r}, {0.y!r})'.format(self)
    
        def __str__(self):
            return '({0.x!s}, {0.y!s})'.format(self)
    
        def __eq__(self, other):
            if self.x == other.x and self.y == other.y:
                return True
            else:
                return False
    
    
    p = Pair(3, 4)
    pp = eval(repr(p))
    if pp == p:
        print('true')
    
  • __getitem__(),__setitem__(),__delitem__()

    __getitem__()将对象当作list使用,如obj = ACLASS(), obj_first=obj[0]

    class A(object):
        def __init__(self):
            self['B'] = "BB"
            self['D'] = "DD"
            self.jj = 'jj'
            del self['D']
    
        def __setitem__(self, name, value):
            """
                每当属性被赋值的时候都会调用该方法
            """
            print("__setitem__:Set %s Value %s" % (name, value))
            self.__dict__[name] = value
    
        def __getitem__(self, name):
            """
                当访问属性时会调用该方法
            """
            print("__getitem__:Try to get attribute named '%s'" % name)
            if hasattr(self, name):
                return getattr(self, name)
            return None
    
        def __delitem__(self, name):
            """
                当删除属性时调用该方法
            """
            print("__delitem__:Delect attribute '%s'" % name)
            del self.__dict__[name]
            print(self.__dict__)
    
    
    if __name__ == "__main__":
        X = A()
        b = X['jj']
        print(b)
    
  • __getattr__(),__setattr__(),__delattr__()

     为getattr(),del obj.attr,setattr(),hasattr()提供接口,这是魔法方法中的一个难点,并且发现不少其他博客居然讲错了,但也许是py3和py2的差别,请参考官方文档-py3属性设置。需要铭记实例对象属性寻找的顺序如下:

    1. 首先访问 __getattribute__() 魔法方法(隐含默认调用,无论何种情况,均会调用此方法
    2. 去实例对象t中查找是否具备该属性: t.__dict__ 中查找,每个类和实例对象都有一个 __dict__ 的属性
    3. 若在 t.__dict__ 中找不到对应的属性, 则去该实例的类中寻找,即 t.__class__.__dict__
    4. 若在实例的类中也招不到该属性,则去父类中寻找,即 t.__class__.__bases__.__dict__中寻找
    5. 若以上均无法找到,则会调用 __getattr__ 方法,执行内部的命令(若未重载 __getattr__ 方法,则直接报错:AttributeError)
    #! /usr/bin/python
    # _*_ coding: utf-8 _*_
    
    
    class Test(object):
        def __init__(self, count):
            self.count = count
    
        def __getattr__(self, name):
            print('__getattr__')
            raise AttributeError('no such field')
    
        def __getattribute__(self, item):
            """
            一旦重载了 __getattribute__() 方法, 如果找不到属性,
            则必须要手动加入第④步, 否则无法进入到 第⑤步 (__getattr__)的
            :param item:
            :return:
            """
    
            print('__getattribute__')
            return super().__getattribute__(item)
    
        def __setattr__(self, name, value):
            print('__setattr__')
            super().__setattr__(name, value)
    
        def __delattr__(self, name):
            print('__delattr__')
            super().__delattr__(name)
    
    # 希望读者好好体会下面测试代码的输出,并思考为什么
    t = Test(3)
    print(t.count)
    print('*'*40)
    
    t.x = 10  # __setattr__
    a = t.x  # __getattribute__
    print(a)  # 10
    
    print('**', t.__dict__)
    # __getattribute__
    # ** {'x': 10}
    
    if 'x' in t.__class__.__bases__[0].__dict__:
        print('** x in base class dict')
    else:
        print('** x not in base class dict')
    # __getattribute__
    # ** x not in base class dict
    
    del t.x  # __delattr__
    
    a = t.x
    # __getattribute__
    # __getattr__
    
    

    summary:

    __getattr__(self, name): 访问不存在的属性时调用

    __getattribute__(self, name):访问存在的属性时调用(先调用该方法,查看是否存在该属性,若不存在,接着去调用①)

    __setattr__(self, name, value):设置实例对象的一个新的属性时调用

    __delattr__(self, name):删除一个实例对象的属性时调用

  • __gt__(),__lt__(),__eq__(),__ne__(),__ge__()

     定义对象比较方法,为关系运算符>,>=,<,==等提供接口,这就类似于C++里面的运算符重载。

    class SavingsAccount(object):
        def __init__(self, name, pin, balance=0.0):
            self._name = name
            self._pin = pin
            self._balance = balance
    
        def __lt__(self, other):
            return self._balance < other._balance
    
    
    s1 = SavingsAccount("Ken", "1000", 0)
    s2 = SavingsAccount("Bill", "1001", 30)
    print(s1 < s2)
    

     其实,类似的数值运算的魔法方法非常非常之多,好吧,我真的用了两个,现在是三个非常来形容,以下列出部分,希望读者根据上面的例子举一反三:

    class Point(object):
        def __init__(self):
            self.x = -1
            self.y = 5
    
        # 正负,取反、绝对值
        def __pos__(self):
            pass
    
        def __neg__(self):
            pass
    
        def __invert__(self):
            pass
    
        def __abs__(self):
            pass
    
        # 加减乘除、取余,指数
        def __add__(self, other):
            pass
    
        def __sub__(self, other):
            pass
    
        def __divmod__(self, other):
            pass
    
        def __mul__(self, other):
            pass
    
        def __mod__(self, other):
            pass
    
        def __pow__(self, power, modulo=None):
            pass
    
        # 逻辑运算符
        def __and__(self, other):
            pass
    
        def __or__(self, other):
            pass
    
        def __xor__(self, other):
            pass
    
        # 位移
        def __lshift__(self, other):
            pass
    
        def __rshift__(self, other):
            pass
    
        # 赋值语句
        def __iadd__(self, other):
            pass
    
        def __imul__(self, other):
            pass
    
        def __isub__(self, other):
            pass
    
        def __idiv__(self, other):
            pass
    
        def __imod__(self, other):
            pass
    
        def __ipow__(self, other):
            pass
    
        def __ilshift__(self, other):
            pass
    
        def __irshift__(self, other):
            pass
    
    
  • __get__(),__set__(),__delete__()

     实现了这些方法的类,称之为描述器类,这里不展开讲,仅给出一个实例

    
    class Integer:
        """
        资料描述器和非资料描述器会产生不一样的优先级,
        """
    
        def __init__(self, name):
            self.name = name
    
        def __get__(self, instance, cls):
            if instance is None:
                return self
            else:
                return instance.__dict__[self.name]
    
        def __set__(self, instance, value):
            if not isinstance(value, int):
                raise TypeError('Expected an int')
            instance.__dict__[self.name] = value
    
        def __delete__(self, instance):
            del instance.__dict__[self.name]
    
    
    class Point:
        x = Integer('x')
        y = Integer('y')
    
        def __init__(self, x, y):
            """
            特别注意因为Interger是资料描述器,所以self.x 其实指的就是x = Integer('x')
            """
            self.x = x
            self.y = y
    
    
    p = Point(10, 11)
    
  • __iter__()

      为for … in提供接口,返回一个迭代对象,并调用对象next()方法,直到StopIteration。以下这个例子来源廖雪峰python教程

    class Fib(object):
        def __init__(self):
            self.a, self.b = 0, 1  # 初始化两个计数器a,b
    
        def __iter__(self):
            return self  # 实例本身就是迭代对象,故返回自己
    
        def __next__(self):
            self.a, self.b = self.b, self.a + self.b  # 计算下一个值
            if self.a > 10:  # 退出循环的条件
                raise StopIteration()
            return self.a  # 返回下一个值
    
    
    for i in Fib():
        print(i)
    
  • __dir__()

     为dir()函数提供接口,查看一个对象中有哪些属性和方法, 返回值应该是iterable。【关于Iterable放在后面讨论】,如返回一个list,一般而言我们不去重写这个方法。

    首先我们看看dir函数:

    >>> res = dir(str(123))
    >>> print(type(res))
    <class 'list'>
    >>> print(res)
    ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
    

    我们自定义一个__dir__()方法试试:

    
    class A(object):
        def __dir__(self):
            print('不给你看这个函数有什么属性和方法,哼、、哼哼')
            return []
    
    
    if __name__ == "__main__":
        a = A()
        result = dir(a)
        print(result)
        
    # 不给你看这个函数有什么属性和方法,哼、、哼哼
    # <class 'list'>
    
  • __call__()

     假设有对象obj, 则obj()可以调用__call__()方法

    class A(object):
        def _pre_process(self):
            print('preoprocess data')
    
        def __call__(self, *args, **kwargs):
            self._pre_process()
            print('object called over')
    
    
    A()()  # 调用了类中__call__()中的逻辑
    
  • 数据类型转换

    # 类似数值计算的魔法方法,实现以下方法就可以实现自定义的数据类型转换
    class A(object):
        def __init__(self, num):
            self.x = num
    
        def __int__(self):
            pass
    
        def __float__(self):
            pass
    
        def __long__(self):
            pass
    
        def __complex__(self):
            pass
    
        def __bytes__(self):
            pass
    
        def __oct__(self):
            pass
    
        def __hex__(self):
            pass
    
        def __str__(self):
            pass
    
        def __nonzero__(self):
            """
            这里仅仅实现bool转换,其他的读者可举一反三
            """
            if self.x != 0:
                return True
            else:
                return False
    
    
    if __name__ == '__main__':
        print(bool(A(5)))
    
    
  • __enter__(),__exit__()

     这两个魔法方法使对象是用于With会话管理,一般用于需要打开关闭的场景,如文件读写、数据库连接。

    #! /usr/bin/python
    # _*_ coding: utf-8 _*_
    
    
    class OpenTextFile(object):
        """
        实现打开一个文件使用with,这里仅仅是个例子
        因为 open()本身就可以实现 with context
        """
        def __init__(self, file_path):
            self.file = file_path
    
        def __enter__(self):
            self.f = open(self.file, 'r', encoding='utf-8')
            return self.f
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.f.close()
    
    
    with OpenTextFile('./with_context.py') as f:
        print(f.readlines())
    
  • 其他魔法方法

    # 获取对象大小
    __sizeof__()
    
    # 获取对象哈希值
    __hash__()
    
    # 用于对象序列化和反序列化
    __reduce__()
    __reduce_ex__()
    
    __instancecheck__(self, instance)  # 检查一个实例是不是你定义的类的实例
    __subclasscheck__(self, subclass)  # 检查一个类是不是你定义的类的子类
    
    __contains__(self,value)  # 使用in操作测试关系时
    __concat__(self,value)  # 连接两个对象时
    __index__(self)  # 对象被当作list索引时
    

辅助知识

OOP实用函数

  • type() 查看对象类型
  • issubclass() 查看一个对象是否属于某个类的子类
  • is vs == vs isinstance 前者查看两个对象是否严格相同,后者查看某对象是否属于某类
  • id用于获取对象的内存地址

迭代器生成器

  • 列表生成式

  List Comprehensions, 是python内置的用来生成list的一种快捷高效的方式。

# 举例
[x * x for x in range(1, 11)]
[x * x for x in range(1, 11) if x % 2 == 0]
  • 生成器

  Generator,是列表生成式的一种优化方案,列表生成式的list会直接放在内存中,因此其大小必然会受到内存的限制;而生成器就是为了解决这种资源耗费的情况,能够做到先定义,边循环边计算。

# 注意区分生成器和列表生成式的定义方式
# 生成器是用()、列表生成式是用[]

>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000020A6184D0A0>

# 如果要一个一个打印出来,可以通过next()或则__next__()获得generator的下一个返回值
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> g.__next__()
16

# 在实际编程中,我们一般这样使用
for n in g:
    print(n)

  在实际应用中,我们经常会借助yield关键字构造生成器函数,如斐波那契函数:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1

>>> g = fib(6)
>>> g
<generator object fib at 0x0000020A619C6BF8>
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
>>> next(g)
8
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
  • 迭代器

  可以直接作用于for循环的对象统称为可迭代对象(Iterable),包括集合数据类型(如listtupledictsetstr)和 generator(生成器、带yield的generator function)。但是集合数据类型和generator有一个很大的区别:generator可以使用next()不断调用,直至StopIteration。在python中,可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

Tips: 如果一个对象是 Iterator(迭代器), 那么它必然是一个Iterable(可迭代对象);而Iterable不一定是一个Iterator,但是Iterable可以通过iter()函数变成Iterator;特殊的是generator既是Iterable又是Iterator。

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True

>>> isinstance([], Iterator)
False
>>> isinstance('abc', Iterator)
False

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值