9面对对象的程序设计

文章目录


类是一种数据结构,可包含数据成员和函数成员.在程序设计中可以定义类,并创建和使用其实例.

9.1 面向对象概念

9.1.1 对象的定义

在计算机科学中,对象是指一种数据结构,它包含了一些数据和与这些数据相关的操作。对象的数据被称为其状态,而对象的操作被称为其方法。对象可以看作是一种封装了数据和方法的实体。

对象是面向对象编程的基本概念之一。在面向对象编程中,对象是程序的基本单元,程序的行为由一组相互作用的对象之间的交互所决定。对象通常被创建、使用和销毁,并且它们之间可以进行通信和协作,以实现程序的功能。

每个对象都有一个类型,它定义了对象具有的属性和方法。类型可以看作是对象的模板或蓝图,它描述了对象的基本结构和行为。在面向对象编程中,通过定义不同的类型,可以创建多个具有不同属性和行为的对象。

9.1.2 封装,继承,多态

封装、继承和多态是面向对象编程中的三个基本概念,它们是实现面向对象编程的重要手段。

  1. 封装(Encapsulation):封装是面向对象编程的核心概念之一。它指的是将数据和行为封装在一个单独的单元中,即对象。对象的内部状态和行为对外部是不可见的,只有通过对象的接口才能访问和操作对象。封装可以提高程序的安全性和可维护性,同时也可以隐藏复杂性,使程序更易于理解和使用。

  2. 继承(Inheritance):继承是一种从已有类派生出新类的机制。通过继承,新类可以继承已有类的属性和方法,并可以添加新的属性和方法,从而扩展和定制已有类的功能。继承可以提高代码的重用性和可扩展性,同时也可以使代码更易于维护和理解。

  3. 多态(Polymorphism):多态是指同一种操作可以作用于不同的对象,并能够产生不同的结果。在面向对象编程中,多态可以通过方法重载和方法重写来实现。方法重载是指在同一个类中定义多个同名但参数不同的方法,而方法重写是指子类重新定义父类的方法。多态可以提高程序的灵活性和扩展性,同时也可以使程序更易于理解和使用。

这三个概念是面向对象编程中非常重要的,它们相互关联,相互依赖,共同构成了面向对象编程的核心思想。

9.1.3 类对象和实例对象

在面向对象编程中,类对象和实例对象是两个重要的概念。

  1. 类对象(Class object):类对象是用来创建实例对象的模板。在Python中,每个类都是一个对象,这个对象包含了类的名称、属性和方法等信息。通过类对象可以访问类的属性和方法,也可以用来创建实例对象。

  2. 实例对象(Instance object):实例对象是由类对象创建出来的对象。每个实例对象都有自己的状态和行为,它们共享类对象的属性和方法,但是它们的状态是独立的。实例对象可以调用类的方法和属性,也可以添加自己的属性和方法。

在Python中,创建实例对象的语法是通过调用类的构造函数来实现的。例如,对于一个叫做"Person"的类,可以通过以下语句来创建一个实例对象:

p = Person()

这个语句会调用Person类的构造函数来创建一个新的实例对象p。实例对象p可以访问Person类的属性和方法,也可以添加自己的属性和方法。

类对象和实例对象在面向对象编程中都扮演了重要的角色。类对象是创建实例对象的模板,它定义了实例对象的属性和方法;而实例对象则是根据类对象创建出来的对象,它可以调用类的属性和方法,也可以添加自己的属性和方法。

9.3 属性

属性实际上是类中的变量.

9.3.2 实例对象属性和类对象属性

在面向对象编程中,实例对象可以有自己的属性。实例属性是指每个实例对象所特有的属性,它们与类属性不同,类属性是所有实例对象所共享的属性。

实例属性可以在实例化对象时动态添加,也可以通过方法对其进行修改。可以使用点操作符"."来访问实例属性,如下所示:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p1 = Person("Alice", 20)
print(p1.name)  # 输出 "Alice"
print(p1.age)   # 输出 20

p1.name = "Bob"
print(p1.name)  # 输出 "Bob"

在上面的例子中,我们定义了一个Person类,并在构造函数__init__()中初始化了实例属性nameage。然后,我们创建了一个名为p1的实例对象,并使用点操作符访问了它的属性。最后,我们修改了实例属性name的值,并再次输出它的值。

需要注意的是**,实例属性只对当前实例对象有效,不会影响其他实例对象或类属性的值。这就是实例属性和类属性的区别。**

在面向对象编程中,类对象属性是指被类所共享的属性,它们在类定义时就已经被定义好,并且对所有实例对象都是相同的。

在Python中,类对象属性通常定义在类的内部,但是在所有方法的外部。定义类属性的语法是在类的内部使用类名来定义一个变量,如下所示:

class Person:
    species = "human"

    def __init__(self, name, age):
        self.name = name
        self.age = age

p1 = Person("Alice", 20)
print(p1.species)  # 输出 "human"

在上面的例子中,我们定义了一个名为species的类属性,并将其值设置为"human"。然后,我们创建了一个名为p1的实例对象,并使用点操作符访问了它的类属性species

需要注意的是,类属性可以被所有实例对象共享,并且可以通过类名来访问。而实例属性只对当前实例对象有效,不会影响其他实例对象或类属性的值。因此,在使用类属性时需要注意避免对其进行意外修改,以免影响到其他实例对象。

9.3.3 私有属性和公有属性

在Python中,私有属性和公有属性是面向对象编程中的一个重要概念。

  1. 私有属性(Private attribute):私有属性是指只能在类的内部访问的属性,外部无法直接访问。在Python中,可以通过在属性名前面加上两个下划线"__"来定义私有属性。例如:
class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

在上面的例子中,我们定义了两个私有属性__name__age,它们只能在类的内部访问,外部无法直接访问。如果在外部使用点操作符来访问这些私有属性,会导致AttributeError异常。

  1. 公有属性(Public attribute):公有属性是指可以在类的内部和外部都可以访问的属性。在Python中,没有特殊的语法来定义公有属性,所有没有定义为私有属性的属性都是公有属性。例如:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

在上面的例子中,我们定义了两个公有属性nameage,它们可以在类的内部和外部都可以访问。

需要注意的是,在Python中私有属性并不是真正的私有,它们只是在名称前面加上了两个下划线,Python解释器将其名称进行了重命名,使其不容易被外部访问到。但是如果非要访问私有属性,可以通过特殊的名称重命名规则来访问。因此,Python中的私有属性并不是严格意义上的私有,而是一种约定。

9.3.4 @property装饰器

在Python中,@property是一种装饰器,用于将类的方法转换为属性,从而实现属性的访问和修改。

@property装饰器可以将一个方法转换为只读属性,这意味着这个属性只能被读取,而不能被修改。同时,@property装饰器还可以与setter装饰器一起使用,从而实现可读写的属性。

下面是一个使用@property装饰器的例子:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    @property
    def area(self):
        return self.width * self.height

r = Rectangle(5, 10)
print(r.area)  # 输出 50

在上面的例子中,我们定义了一个名为Rectangle的类,它有两个属性widthheight,以及一个@property装饰器修饰的方法area。这个方法计算并返回矩形的面积,但是它被定义为只读属性,因此不能被修改。

使用@property装饰器可以使方法看起来像一个属性,从而使代码更加清晰和易读。同时,它还可以提供一种简单的方式来控制属性的访问和修改,从而增强程序的安全性和可维护性。

在Python中,gettersetterdeleter方法是用于访问和修改类属性的方法,它们通常与@property装饰器一起使用。

  1. getter方法:getter方法可以用于获取属性的值,它是一个只读方法,通常被定义为一个使用@property装饰器的方法。例如:
class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height
    
    @property
    def width(self):
        return self._width

    @property
    def height(self):
        return self._height

r = Rectangle(5, 10)
print(r.width)  # 输出 5

在上面的例子中,我们定义了一个名为Rectangle的类,它有两个属性widthheight,以及两个使用@property装饰器修饰的方法widthheight。这两个方法可以用于获取属性的值,但是不能用于修改属性的值。

  1. setter方法:setter方法可以用于设置属性的值,它是一个可写方法,通常被定义为一个使用@属性名.setter装饰器的方法。例如:
class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height
    
    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        self._width = value

r = Rectangle(5, 10)
print(r.width)  # 输出 5

r.width = 8
print(r.width)  # 输出 8

在上面的例子中,我们定义了一个名为Rectangle的类,它有一个属性width和一个使用@width.setter装饰器修饰的方法width。这个方法可以用于设置属性的值。

  1. deleter方法:deleter方法可以用于删除属性,它是一个可写方法,通常被定义为一个使用@属性名.deleter装饰器的方法。例如:
class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height
    
    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        self._width = value

    @width.deleter
    def width(self):
        del self._width

r = Rectangle(5, 10)
print(r.width)  # 输出 5

del r.width
print(r.width)  # 抛出 AttributeError 异常

在上面的例子中,我们定义了一个名为Rectangle的类,它有一个属性width和一个使用@width.deleter装饰器修饰的方法width。这个方法可以用于删除属性。

9.3.5 特殊属性

双下划线开始和结束的属性.

特殊属性含义实例
__name__模块名或类名__name__是内置变量,用于表示当前模块或当前类的名称。例如:print(__name__)可以输出当前模块的名称。
__doc__文档字符串__doc__是内置变量,用于表示当前对象的文档字符串。文档字符串是在定义函数、类或模块时所写的字符串,用于描述函数、类或模块的功能和使用方法。例如:print(Rectangle.__doc__)可以输出Rectangle类的文档字符串。
__init__构造函数__init__是一个特殊方法,用于创建对象时进行初始化操作。例如:p = Person(name='Alice', age=20)会调用Person类的__init__方法来进行对象初始化。
__str__对象的字符串表示__str__是一个特殊方法,用于返回对象的字符串表示。例如:print(p)会调用p对象的__str__方法来输出对象的字符串表示。
__repr__对象的字符串表示__repr__是一个特殊方法,用于返回对象的字符串表示。与__str__方法不同的是,__repr__方法应该返回一个可以用于重新创建该对象的字符串表示。例如:print(repr(p))会调用p对象的__repr__方法来输出对象的字符串表示。
__getattr__访问不存在的属性时的行为__getattr__是一个特殊方法,用于访问不存在的属性时的行为。它会在对象访问不存在的属性时被调用,并返回一个值或抛出一个异常。例如:p.gender会调用p对象的__getattr__方法,如果不存在gender属性,则会返回一个默认值或抛出一个异常。
__setattr__设置属性时的行为__setattr__是一个特殊方法,用于设置属性时的行为。它会在对象设置属性时被调用,并允许在设置属性时执行一些额外的操作。例如:p.name = 'Bob'会调用p对象的__setattr__方法,该方法可以在设置name属性时执行一些额外的操作。
__delattr__删除属性时的行为__delattr__是一个特殊方法,用于删除属性时的行为。它会在对象删除属性时被调用,并允许在删除属性时执行一些额外的操作。例如:del p.name会调用p对象的__delattr__方法,该方法可以在删除name属性时执行一些额外的操作。
__call__对象调用时的行为__call__是一个特殊方法,用于定义对象调用时的行为。它允许将对象当作函数来调用,并执行一些额外的操作。例如:p()会调用p对象的__call__方法,该方法可以在对象调用时执行一些额外的操作。

需要注意的是,Python中还有许多其他的特殊属性,这里只列举了一部分常见的特殊属性。每个特殊属性都有其独特的含义和用途,它们可以帮助我们更好地理解Python的面向对象编程模型。

9.3.6 自定义属性

python中可以赋予一个对象自定义的属性,即类定义中不存在的属性.对象通过特殊属性__dict__存储自定义属性.

9.4 方法

9.4.1 对象实例方法,静态方法,类方法

在Python中,对象的方法可以分为实例方法、静态方法和类方法。每种方法类型都有其特定的调用方式和用途。

  1. 对象实例方法
    对象实例方法是最常见的一种方法类型,它可以通过类的实例来调用。对象实例方法的第一个参数通常是self,用于表示当前对象的引用。例如:
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    def say_hello(self):
        print(f"Hello, my name is {self._name} and I am {self._age} years old.")

p = Person("Alice", 30)
p.say_hello()  # 输出 "Hello, my name is Alice and I am 30 years old."

在上面的例子中,我们定义了一个名为Person的类,它有一个方法say_hello,用于打印人的姓名和年龄。这个方法是一个对象实例方法,它可以通过类的实例来调用。

  1. 静态方法
    静态方法是一种不需要访问对象状态的方法,它可以通过类来调用。静态方法通常用于执行通用功能,例如字符串格式化或计算数学函数。静态方法使用@staticmethod装饰器来定义,它不需要self参数。例如:
class Math:
    @staticmethod
    def add(x, y):
        return x + y

result = Math.add(3, 5)
print(result)  # 输出 8

在上面的例子中,我们定义了一个名为Math的类,它有一个静态方法add,用于计算两个数的和。这个方法是一个静态方法,它可以通过类来调用。

  1. 类方法
    类方法是一种可以访问类状态的方法,它也可以通过类来调用。类方法通常用于创建或操作类级别的状态,例如计算类的大小或创建单例对象。类方法使用@classmethod装饰器来定义,它的第一个参数通常是cls,用于表示当前类的引用。例如:
class Person:
    total_count = 0  # 类级别的状态

    def __init__(self, name, age):
        self._name = name
        self._age = age
        Person.total_count += 1

    @classmethod
    def get_total_count(cls):
        return cls.total_count

p1 = Person("Alice", 30)
p2 = Person("Bob", 40)

print(Person.get_total_count())  # 输出 2

在上面的例子中,我们定义了一个名为Person的类,它有一个类级别的属性total_count和一个类方法get_total_count。在每次创建新的Person对象时,total_count的值会加1。get_total_count方法可以用于获取当前类创建的对象的数量,它是一个类方法,可以通过类来调用。

需要注意的是,对象实例方法、静态方法和类方法都有其独特的用途和特点。在使用时,我们应该根据具体的需求来选择合适的方法类型。

9.4.2 __init__()方法和__new__()方法

在Python中,每个类都有两个重要的方法:__new__()__init__()。这两个方法在创建对象时都会被调用,但它们的作用不同。

  1. __new__()方法
    __new__()方法是一个特殊的静态方法,用于创建对象并返回创建的对象实例。该方法的第一个参数是类的引用,后面的参数是传递给类构造函数的参数。__new__()方法必须返回一个新的对象实例。在创建对象时,Python会首先调用__new__()方法来创建对象实例,然后再调用__init__()方法进行初始化。如果__new__()方法没有返回对象实例,则__init__()方法不会被调用。例如:
class Person:
    def __new__(cls, *args, **kwargs):
        print("Creating object...")
        return super().__new__(cls)

    def __init__(self, name, age):
        print("Initializing object...")
        self._name = name
        self._age = age

p = Person("Alice", 30)

在上面的例子中,我们定义了一个名为Person的类,它重写了__new__()方法和__init__()方法。在__new__()方法中,我们打印了一条消息来显示对象正在被创建。在__init__()方法中,我们用传递给构造函数的参数初始化了对象的状态。当我们创建Person对象时,Python首先调用__new__()方法来创建对象实例,然后再调用__init__()方法进行初始化。

  1. __init__()方法
    __init__()方法是一个特殊的实例方法,用于初始化对象的状态。该方法的第一个参数是类的实例,后面的参数是传递给类构造函数的参数。在创建对象时,Python会首先调用__new__()方法来创建对象实例,然后再调用__init__()方法进行初始化。例如:
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

p = Person("Alice", 30)

在上面的例子中,我们定义了一个名为Person的类,它有一个__init__()方法,用于初始化对象的状态。当我们创建Person对象时,Python会首先调用__new__()方法来创建对象实例,然后再调用__init__()方法进行初始化。在__init__()方法中,我们用传递给构造函数的参数初始化了对象的状态。

需要注意的是,__new__()方法和__init__()方法都是Python中重要的方法,但它们的作用不同。__new__()方法用于创建对象实例,而__init__()方法用于初始化对象的状态。在使用时,我们应该根据具体的需求来适当地重写这些方法。

9.4.3 __del__()方法

__del__()方法是一个特殊的实例方法,也叫做析构函数。它在对象被销毁时自动被调用,用于释放对象占用的资源。当对象的引用计数为0时,Python会自动调用__del__()方法,来释放对象的资源。例如:

class Person:
    def __init__(self, name):
        self._name = name
        print(f"{self._name} is created")

    def __del__(self):
        print(f"{self._name} is deleted")

p1 = Person("Alice")
p2 = p1

del p1
del p2

在上面的例子中,我们定义了一个名为Person的类,它有一个__del__()方法,用于在对象被销毁时释放资源。我们创建了一个Person对象p1,然后又将其赋值给了p2。当我们删除p1p2时,Python会自动调用__del__()方法来释放对象的资源。

需要注意的是,__del__()方法并不是完全可靠的,因为它的调用时间是不确定的。当对象的引用计数为0时,Python会自动调用__del__()方法,但是在某些情况下,对象可能永远不会被销毁,例如循环引用的情况。因此,在编写Python程序时,我们应该尽量避免使用__del__()方法,而是采用其他方式来释放资源。

9.4.4 私有方法与公有方法

在Python中,方法前面添加双下划线__的方法被称为私有方法,而没有添加双下划线的方法被称为公有方法。私有方法和公有方法的主要区别在于它们的访问权限。

  1. 私有方法
    私有方法只能在类的内部被访问,无法在类的外部直接访问。私有方法通常用于实现类的内部细节,防止外部对象直接访问和修改类的状态。在Python中,私有方法可以通过在方法名前添加双下划线来定义。例如:
class Person:
    def __init__(self, name):
        self.__name = name

    def __display_name(self):
        print(f"My name is {self.__name}")

p = Person("Alice")
p.__display_name()  # 报错:'Person' object has no attribute '__display_name'

在上面的例子中,我们定义了一个名为Person的类,它有一个私有方法__display_name,用于显示人的姓名。在创建Person对象时,我们将人的姓名存储在私有属性__name中。当我们在类的外部直接调用__display_name方法时,Python会报错,因为私有方法只能在类的内部被访问。

虽然Python中存在私有方法,但是在实际编程中,我们往往不会强制限制外部对象访问和修改类的状态,而是通过命名约定来表示某个方法或属性是私有的,例如在方法名前添加单下划线_

  1. 公有方法
    公有方法可以在类的内部和外部被访问,它们通常用于实现类的公有接口,提供给外部对象使用。在Python中,公有方法可以通过直接定义方法来实现。例如:
class Person:
    def __init__(self, name):
        self.name = name

    def display_name(self):
        print(f"My name is {self.name}")

p = Person("Alice")
p.display_name()  # 输出 "My name is Alice"

在上面的例子中,我们定义了一个名为Person的类,它有一个公有方法display_name,用于显示人的姓名。在创建Person对象时,我们将人的姓名存储在属性name中。当我们调用display_name方法时,Python会输出人的姓名。

需要注意的是,虽然在Python中存在私有方法和公有方法的访问权限的区别,但是这并不是强制性的,Python的哲学是“成人之间的协议”,即应该通过命名约定来表示某个方法或属性是私有的,而不是强制限制外部对象访问和修改类的状态。

9.4.5 方法的重载

Python中的方法重载指的是在同一个类中定义多个同名方法,但方法的参数个数或类型不同,从而使它们具有不同的行为。在其他一些编程语言中,方法重载是通过方法的签名(即方法名和参数类型)来实现的,但是在Python中,方法重载是通过参数的默认值和可变参数来实现的。

例如,我们可以定义一个名为add的类,它有两个方法addadd_numbers,它们的参数个数和类型不同,从而实现不同的行为。

class Add:
    def add(self, a, b):
        return a + b

    def add_numbers(self, *args):
        return sum(args)

a = Add()
print(a.add(1, 2))           # 输出 3
print(a.add_numbers(1, 2, 3)) # 输出 6

在上面的例子中,我们定义了一个名为Add的类,它有两个同名方法addadd_numbers,它们的参数个数和类型不同。在add方法中,我们传递了两个参数ab,计算它们的和并返回。在add_numbers方法中,我们使用可变参数*args来传递任意数量的参数,并计算它们的和并返回。

需要注意的是,在Python中,方法重载并不是必须的,因为Python是一种动态类型语言,方法的参数类型可以在运行时动态确定,而不需要在编译时确定。因此,在Python中,我们通常不需要使用方法重载来实现不同的行为,而是尽可能使用默认值和可变参数来适应不同的场景。

9.5 继承

9.5.1 派生类

在面向对象编程中,派生类是指继承自其他类的子类。派生类可以继承父类的属性和方法,并且可以添加新的属性和方法,从而扩展父类的功能。在Python中,我们可以使用关键字class来定义一个派生类,并使用关键字super来调用父类的方法。例如:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    def display(self):
        print(f"My name is {self._name}, I'm {self._age} years old")

class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self._grade = grade

    def display(self):
        super().display()
        print(f"I'm a student in grade {self._grade}")

s = Student("Alice", 18, 12)
s.display()  # 输出 "My name is Alice, I'm 18 years old\nI'm a student in grade 12"

在上面的例子中,我们定义了一个名为Person的父类,它有一个__init__方法和一个display方法。然后我们定义了一个名为Student的派生类,它继承自Person类,并添加了一个__init__方法和一个display方法。在__init__方法中,我们使用super()函数来调用父类__init__方法,并添加了一个新的属性_grade。在display方法中,我们使用super()函数来调用父类display方法,并在其基础上添加了新的输出。

需要注意的是,派生类可以覆盖父类的方法,从而实现不同的行为。在上面的例子中,我们重写了父类的display方法,并添加了新的输出。当我们调用display方法时,Python会自动调用派生类的方法,而不是父类的方法。

9.5.2 查看继承的层次关系

在Python中,可以使用mro()方法来查看一个类的继承层次关系。

mro()方法返回一个元组,表示当前类的方法解析顺序。方法解析顺序指的是Python在查找方法时,按照继承层次从下到上、从左到右的顺序进行查找的顺序。例如:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.mro())  # 输出 [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

在上面的例子中,我们定义了4个类ABCD,其中BC继承自AD继承自BC。当我们使用mro()方法查看D类的继承层次关系时,Python会依次输出DBCAobject,表示方法解析顺序为DBCAobject。这意味着,当我们调用D类的方法时,Python会首先在D类中查找,如果找不到就依次在BCAobject类中查找,直到找到或者查找完所有类为止。

需要注意的是,mro()方法只能在新式类中使用,不能在经典类中使用。在Python 2.x中,默认情况下定义的类都是经典类,在Python 3.x中默认情况下定义的类都是新式类。如果需要在Python 2.x中使用新式类,可以在类定义时继承自object类。例如:

class A(object):  # 继承自 object 类
    pass

9.5.3 类成员的继承和重写

类成员(属性和方法)的继承和重写是面向对象编程中的重要概念。在Python中,派生类可以继承其父类的属性和方法,并且可以重写父类的方法来实现不同的行为。下面对类成员的继承和重写进行详细说明。

  1. 属性的继承
    派生类可以继承其父类的属性,这意味着派生类可以直接访问父类的属性,而不需要重新定义它们。例如:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Student(Person):
    pass

s = Student("Alice", 18)
print(s.name)  # 输出 "Alice"
print(s.age)   # 输出 18

在上面的例子中,我们定义了一个名为Person的父类,它有两个属性nameage。然后我们定义了一个名为Student的派生类,它继承自Person类。在创建Student对象时,我们可以直接访问父类的属性nameage,而不需要重新定义它们。

  1. 方法的继承
    派生类可以继承其父类的方法,这意味着派生类可以直接调用父类的方法,而不需要重新定义它们。例如:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def display(self):
        print(f"My name is {self.name}, I'm {self.age} years old")

class Student(Person):
    pass

s = Student("Alice", 18)
s.display()  # 输出 "My name is Alice, I'm 18 years old"

在上面的例子中,我们定义了一个名为Person的父类,它有一个display方法。然后我们定义了一个名为Student的派生类,它继承自Person类。在创建Student对象时,我们可以直接调用父类的display方法,而不需要重新定义它。

  1. 方法的重写
    派生类可以重写其父类的方法,从而实现不同的行为。在派生类中,如果定义了与父类同名的方法,则该方法会覆盖父类的方法。例如:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def display(self):
        print(f"My name is {self.name}, I'm {self.age} years old")

class Student(Person):
    def display(self):
        print(f"My name is {self.name}, I'm {self.age} years old, and I'm a student")

s = Student("Alice", 18)
s.display()  # 输出 "My name is Alice, I'm 18 years old, and I'm a student"

在上面的例子中,我们定义了一个名为Person的父类,它有一个display方法。然后我们定义了一个名为Student的派生类,它继承自Person类,并重写了父类的display方法。在创建Student对象时,我们调用display方法时,Python会自动调用派生类的方法,而不是父类的方法。

9.6 对象的特殊方法

9.6.1 对象的特殊方法概述

下表列出了Python中的一些特殊方法及其含义:

特殊方法含义
__init__(self[, ...])构造方法,在创建对象时自动调用
__str__(self)返回对象的字符串表示,使用print()函数或str()函数时自动调用
__repr__(self)返回对象的字符串表示,通常用于调试和开发中,使用repr()函数时自动调用
__len__(self)返回对象的长度,使用len()函数时自动调用
__getitem__(self, key)获取对象的指定元素,使用[]运算符时自动调用
__setitem__(self, key, value)设置对象的指定元素,使用[]运算符时自动调用
__delitem__(self, key)删除对象的指定元素,使用del语句时自动调用
__iter__(self)返回对象的迭代器,使对象可以被for循环遍历
__next__(self)返回迭代器的下一个元素,使对象可以被for循环遍历
__contains__(self, item)判断对象是否包含某个元素,使用in运算符时自动调用
__add__(self, other)实现对象的加法运算,使用+运算符时自动调用
__sub__(self, other)实现对象的减法运算,使用-运算符时自动调用
__mul__(self, other)实现对象的乘法运算,使用*运算符时自动调用
__truediv__(self, other)实现对象的除法运算,使用/运算符时自动调用
__floordiv__(self, other)实现对象的整数除法运算,使用//运算符时自动调用
__mod__(self, other)实现对象的取模运算,使用%运算符时自动调用
__pow__(self, other[, modulo])实现对象的指数运算,使用**运算符时自动调用
__eq__(self, other)实现对象的相等比较,使用==运算符时自动调用
__ne__(self, other)实现对象的不等比较,使用!=运算符时自动调用
__lt__(self, other)实现对象的小于比较,使用<运算符时自动调用
__le__(self, other)实现对象的小于等于比较,使用<=运算符时自动调用
__gt__(self, other)实现对象的大于比较,使用>运算符时自动调用
__ge__(self, other)实现对象的大于等于比较,使用>=运算符时自动调用
__bool__(self)返回对象的布尔值,使用bool()函数时自动调用
__getattr__(self, name)获取对象的属性,当访问不存在的属性时自动调用
__setattr__(self, name, value)设置对象的属性,当设置不存在的属性时自动调用
__delattr__(self, name)删除对象的属性,当删除不存在的属性时自动调用
__call__(self[, args...])实现对象的调用,使对象可以像函数一样被调用

需要注意的是,这些特殊方法是Python提供的一些约定俗成的方法,用于实现对象的基本操作。通过实现这些特殊方法,我们可以让对象更加自然地与Python内置函数和运算符交互,从而提高代码的可读性和可维护性。

9.6.2 运算符重载与对象的特殊方法

下表列出了一些常见的运算符及其对应的特殊方法和含义:

运算符特殊方法含义
+__add__(self, other)实现对象的加法运算
-__sub__(self, other)实现对象的减法运算
*__mul__(self, other)实现对象的乘法运算
/__truediv__(self, other)实现对象的除法运算
//__floordiv__(self, other)实现对象的整数除法运算
%__mod__(self, other)实现对象的取模运算
**__pow__(self, other[, modulo])实现对象的指数运算
==__eq__(self, other)实现对象的相等比较
!=__ne__(self, other)实现对象的不等比较
<__lt__(self, other)实现对象的小于比较
<=__le__(self, other)实现对象的小于等于比较
>__gt__(self, other)实现对象的大于比较
>=__ge__(self, other)实现对象的大于等于比较
[]__getitem__(self, key)获取对象的指定元素
[]__setitem__(self, key, value)设置对象的指定元素
in__contains__(self, item)判断对象是否包含某个元素
()__call__(self[, args...])实现对象的调用

需要注意的是,Python中的运算符本质上是一些特殊方法的调用,通过实现这些特殊方法,我们可以自定义对象的运算符行为,从而提高代码的灵活性和可维护性。

下面是一个简单的示例,演示了如何重载对象的加法运算符:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __str__(self):
        return f"({self.x}, {self.y})"
    
# 创建两个向量
v1 = Vector(1, 2)
v2 = Vector(3, 4)

# 使用加法运算符计算两个向量的和
v3 = v1 + v2

# 输出结果
print(v3)  # 输出 (4, 6)

在上面的代码中,我们定义了一个Vector类,它包含xy两个属性,表示向量的坐标。我们重载了加法运算符+的特殊方法__add__,使得两个向量可以直接相加,得到它们的和。在__add__方法中,我们创建了一个新的向量,其坐标为两个向量的对应坐标相加的结果,然后返回这个新向量。

在使用加法运算符计算两个向量的和时,Python会自动调用__add__方法,得到它们的和,然后返回一个新的向量。最后,我们使用print()函数输出了新向量的坐标。

9.6.3 @functools.total_ordering装饰器

@functools.total_ordering是一个装饰器,用于简化实现对象的比较操作,特别是当我们只需要实现其中一部分比较操作(例如小于和等于)时。

使用@functools.total_ordering装饰器时,我们只需要实现对象的小于比较__lt__和等于比较__eq__两个特殊方法中的其中一个,装饰器会自动帮我们实现其他比较操作,包括大于比较__gt__、小于等于比较__le__和大于等于比较__ge__。这样,我们就可以用更少的代码实现对象的比较操作。

下面是一个使用@functools.total_ordering装饰器的示例:

import functools

@functools.total_ordering
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __eq__(self, other):
        return self.age == other.age
    
    def __lt__(self, other):
        return self.age < other.age
    
# 创建两个人物对象
p1 = Person("Alice", 25)
p2 = Person("Bob", 30)

# 比较两个人物对象的大小
print(p1 < p2)   # 输出 True
print(p1 <= p2)  # 输出 True
print(p1 == p2)  # 输出 False
print(p1 >= p2)  # 输出 False
print(p1 > p2)   # 输出 False

在上面的代码中,我们定义了一个Person类,表示一个人物对象,包含nameage两个属性。我们使用@functools.total_ordering装饰器修饰这个类,然后只实现了小于比较__lt__和等于比较__eq__两个特殊方法。由于装饰器的作用,Python会自动帮我们实现其他比较操作。最后,我们创建了两个人物对象,然后比较它们的大小,输出比较结果。

需要注意的是,@functools.total_ordering装饰器只能用于实现全序关系的对象,即对象之间可以比较大小并且不会出现环路。如果对象之间的比较关系不是全序关系,我们就不能使用这个装饰器简化代码,而需要手动实现所有比较操作。

9.6.4 __call__()方法和可调用对象

在Python中,函数是一种可调用对象,也就是说,我们可以像调用函数一样调用它们。除了函数以外,还有一些其他类型的对象也可以被调用,它们被称为可调用对象。

一个对象如果想成为可调用对象,就需要实现__call__()方法。当我们将一个可调用对象作为函数调用时,Python会自动调用该对象的__call__()方法,执行相应的操作。

下面是一个使用__call__()方法的示例:

class Counter:
    def __init__(self):
        self.count = 0
    
    def __call__(self):
        self.count += 1
        print(f"Count: {self.count}")
        
# 创建一个计数器对象
counter = Counter()

# 调用计数器对象
counter()  # 输出 Count: 1
counter()  # 输出 Count: 2
counter()  # 输出 Count: 3

在上面的代码中,我们定义了一个Counter类,表示一个计数器对象。我们在__init__()方法中初始化计数器,然后实现了__call__()方法,使得计数器对象可以被调用。在__call__()方法中,我们将计数器加1,然后输出当前的计数值。

在使用计数器对象时,我们直接将它作为函数调用,Python会自动调用__call__()方法,执行相应的操作。在上面的代码中,我们调用了计数器对象三次,每次输出当前的计数值。

需要注意的是,虽然我们可以像调用函数一样调用可调用对象,但它们并不是函数。如果我们想判断一个对象是否是函数,可以使用callable()函数来进行判断。如果一个对象是函数或者实现了__call__()方法,callable()函数会返回True,否则返回False

9.7 对象的引用,浅拷贝和深拷贝

9.7.1 对象的引用

acc10 = ['A',[A,10]]
acc11 = acc10 
id(acc10),id(acc11)  #二者id相同

9.7.2 对象的浅拷贝

在Python中,对象的浅拷贝是指创建一个新的对象,其内容与原对象的内容相同,但是它们的内存地址不同。浅拷贝只会复制原对象的顶层数据,而不会递归地复制其子对象。

Python提供了两种方式来实现对象的浅拷贝:

  1. 使用切片操作符[:]或者copy()方法,可以对列表、集合、字典等可变对象进行浅拷贝。例如:
# 使用切片操作符进行浅拷贝
lst1 = [1, 2, 3]
lst2 = lst1[:]
print(lst2)  # 输出 [1, 2, 3]

# 使用copy()方法进行浅拷贝
set1 = {1, 2, 3}
set2 = set1.copy()
print(set2)  # 输出 {1, 2, 3}
  1. 使用copy模块中的copy()函数,可以对任意对象进行浅拷贝。例如:
import copy

# 对一个自定义对象进行浅拷贝
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(1, 2)
p2 = copy.copy(p1)
print(p2.x, p2.y)  # 输出 1 2

在上面的代码中,我们定义了一个Point类,表示一个点对象。我们使用copy模块中的copy()函数对点对象进行浅拷贝,得到一个新的点对象p2,然后输出它的坐标。

需要注意的是,**浅拷贝只会复制原对象的顶层数据,如果原对象包含了子对象(例如列表、集合、字典等),那么它们的引用地址会被复制到新对象中。这意味着,如果我们修改了新对象中的子对象,原对象也会受到影响。**如果我们想要复制整个对象,包括其子对象,那么可以使用对象的深拷贝。

9.7.3 对象的深拷贝

在Python中,对象的深拷贝是指创建一个新的对象,其内容与原对象的内容相同,但是它们的内存地址不同。深拷贝会递归地复制原对象及其子对象,因此新对象与原对象完全独立,修改新对象不会影响原对象。

Python提供了两种方式来实现对象的深拷贝:

  1. 使用copy模块中的deepcopy()函数,可以对任意对象进行深拷贝。例如:
import copy

# 对一个自定义对象进行深拷贝
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Line:
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2

p1 = Point(1, 2)
p2 = Point(3, 4)
line1 = Line(p1, p2)
line2 = copy.deepcopy(line1)

# 修改新对象中的子对象
line2.p1.x = 5

print(line1.p1.x, line1.p1.y)  # 输出 1 2
print(line2.p1.x, line2.p1.y)  # 输出 5 2

在上面的代码中,我们定义了一个Point类,表示一个点对象,和一个Line类,表示一条线段对象,包含两个点。我们使用copy模块中的deepcopy()函数对线段对象进行深拷贝,得到一个新的线段对象line2,然后修改了它的第一个点对象的x坐标。最后,我们分别输出原对象和新对象中的点对象的坐标,验证它们是否被修改。

  1. 对列表、集合、字典等可变对象进行深拷贝时,可以使用切片操作符[:]或者copy()方法对子对象进行递归地深拷贝。例如:
# 对列表进行深拷贝
lst1 = [[1, 2], [3, 4]]
lst2 = lst1[:]
lst2[0][0] = 5
print(lst1)  # 输出 [[1, 2], [3, 4]]
print(lst2)  # 输出 [[5, 2], [3, 4]]

# 对字典进行深拷贝
dict1 = {'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}}
dict2 = {}
for k, v in dict1.items():
    dict2[k] = v.copy()
dict2['a']['x'] = 5
print(dict1)  # 输出 {'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}}
print(dict2)  # 输出 {'a': {'x': 5, 'y': 2}, 'b': {'x': 3, 'y': 4}}

在上面的代码中,我们分别对列表和字典进行深拷贝,使用切片操作符[:]copy()方法对子对象进行递归地深拷贝,然后修改了新对象中的子对象。最后,我们分别输出原对象和新对象,验证它们是否被修改。

需要注意的是,深拷贝是一种比较耗时的操作,尤其是对于包含大量数据的对象。因此,在进行深拷贝时,需要注意对象的大小和内存占用情况,避免出现性能问题。

9.8 可迭代对象:迭代器和生成器

​ 可循环迭代的对象称为可迭代对象,迭代器和生成器函数是可迭代对象,在Python中提供了定义迭代器和生成器的协议和方法.

​ 相比序列,可迭代对象仅在迭代时产生数据,故可以节省内存空间.Python语言提供了额若干内置可迭代对象,range,map,filter,enumerate,zip;在标准库itertools模块中包含了各种迭代器,这些迭代器高效且内存消耗小.迭代器既可以单独使用,也可以组合使用

9.8.1 可迭代对象

在Python中,可迭代对象是指实现了__iter__()方法的对象,该方法返回一个迭代器对象,用于依次访问容器中的元素。可迭代对象包括列表、元组、集合、字典、字符串等内置的容器类型,以及用户自定义的类对象。

以下是一些常见的可迭代对象:

  • 列表(list)
  • 元组(tuple)
  • 集合(set)
  • 字典(dict)
  • 字符串(str)
  • 文件对象(file)
  • 命名元组(namedtuple)
  • 数据库查询结果(query)
  • 等等

isinstance()函数可以用于判断一个对象是否是可迭代对象。在Python中,可迭代对象是指实现了__iter__()方法的对象。因此,我们可以使用isinstance()函数判断一个对象是否实现了__iter__()方法,从而判断它是否是可迭代对象。

以下是一个示例:

lst = [1, 2, 3]
tup = (4, 5, 6)
dct = {'a': 1, 'b': 2}
strng = "Hello, world!"

print(isinstance(lst, Iterable))    # 输出 True
print(isinstance(tup, Iterable))    # 输出 True
print(isinstance(dct, Iterable))    # 输出 True
print(isinstance(strng, Iterable))  # 输出 True

在上面的代码中,我们使用isinstance()函数判断一个列表、一个元组、一个字典和一个字符串是否是可迭代对象。由于它们都实现了__iter__()方法,因此都被认为是可迭代对象。

需要注意的是,虽然大多数内置容器类型都是可迭代对象,但并不是所有的对象都是可迭代对象。例如,整数、浮点数、布尔值等基本类型的对象都不是可迭代对象。如果尝试对它们进行迭代操作,会抛出TypeError异常。

9.8.2 迭代器

在Python中,迭代器是指实现了__iter__()__next__()方法的对象,它可以用于依次访问容器中的元素。迭代器对象在访问完最后一个元素后,会抛出StopIteration异常,表示迭代已经结束。

以下是一个迭代器对象的示例:

class MyIterator:
    def __init__(self, lst):
        self.lst = lst
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.lst):
            raise StopIteration
        value = self.lst[self.index]
        self.index += 1
        return value

iterator = MyIterator([1, 2, 3, 4, 5])

for i in iterator:
    print(i)

在上面的代码中,我们定义了一个MyIterator类,实现了__iter__()__next__()方法,使它成为一个迭代器对象。__iter__()方法返回迭代器对象本身,__next__()方法返回容器中的下一个元素,如果已经到达最后一个元素,则抛出StopIteration异常。然后我们创建了一个MyIterator对象,并将它用于for循环中进行迭代。

需要注意的是,迭代器对象是一次性的,即在迭代器对象上进行一次遍历后,它的状态会被改变,再次进行遍历时会从上一次结束的位置开始。如果需要再次进行遍历,需要重新创建一个新的迭代器对象。

除了自定义迭代器对象,Python还提供了一些内置的迭代器对象,例如:

  • range():返回一个整数序列的迭代器对象;
  • map():对可迭代对象中的每个元素执行指定的函数,并返回一个迭代器对象;
  • filter():对可迭代对象中的每个元素执行指定的判断条件,并返回一个迭代器对象;
  • zip():将多个可迭代对象中的元素一一对应,并返回一个迭代器对象;
  • enumerate():对可迭代对象中的每个元素进行编号,并返回一个迭代器对象;
  • 等等。

这些内置的迭代器对象可以大大简化代码的编写,提高编程效率。

9.8.5 可迭代对象的迭代:iter()函数和next()函数

在Python中,iter()函数和next()函数是用于操作迭代器对象的内置函数。

iter()函数用于将一个可迭代对象转换为迭代器对象,其语法为:

iter(iterable)

其中,iterable是要转换的可迭代对象。如果iterable本身就是迭代器对象,则直接返回它本身;否则,会调用iterable__iter__()方法,返回一个迭代器对象。

以下是一个使用iter()函数将列表转换为迭代器对象的示例:

lst = [1, 2, 3, 4, 5]
iterator = iter(lst)

print(next(iterator))  # 输出 1
print(next(iterator))  # 输出 2
print(next(iterator))  # 输出 3

在上面的代码中,我们将一个列表对象lst通过iter()函数转换为一个迭代器对象iterator,然后使用next()函数依次访问它的元素。

next()函数用于访问迭代器对象中的下一个元素,其语法为:

next(iterator[, default])

其中,iterator是要访问的迭代器对象,default是可选参数,表示迭代器遍历结束后的返回值。如果迭代器对象已经到达最后一个元素,且没有提供default参数,则会抛出StopIteration异常。

9.8.5 for,while语句进行可迭代对象的迭代

以下是一个使用next()函数访问迭代器对象的示例:

iterator = iter([1, 2, 3, 4, 5])

while True:
    try:
        value = next(iterator)
        print(value)
    except StopIteration:
        break

在上面的代码中,我们创建了一个列表对象[1, 2, 3, 4, 5]的迭代器对象iterator,然后使用while循环和next()函数依次访问它的元素,直到遍历结束。

需要注意的是,如果一个对象不是可迭代对象,或者它的迭代器已经遍历结束,再调用iter()函数或next()函数会抛出TypeErrorStopIteration异常。

9.8.6自定义可迭代对象和迭代器

9.8.7 生成器函数

在Python中,生成器函数是指使用yield语句的函数。生成器函数可以像普通函数一样调用,但是它会返回一个生成器对象,用于生成一系列值。

生成器函数的工作方式与迭代器类似,但是它的实现更加简单、灵活和高效。与迭代器不同的是,生成器函数不需要显式地实现__iter__()__next__()方法,而是使用yield语句来生成值,每次执行到yield语句时,函数会暂停执行,并将生成的值返回给调用方,下次调用时会从暂停的位置继续执行。

以下是一个简单的生成器函数的示例:

def my_generator():
    yield 1
    yield 2
    yield 3
    yield 4
    yield 5

generator = my_generator()

for i in generator:
    print(i)

在上面的代码中,我们定义了一个名为my_generator()的生成器函数,用于生成数字1到5。每次执行到yield语句时,函数会暂停执行,并将生成的值返回给调用方。然后我们创建了一个my_generator()函数的生成器对象,并将它用于for循环中进行迭代。

需要注意的是,生成器函数可以用于惰性计算,即只在需要时才生成值,从而节省内存和计算资源。例如,我们可以使用生成器函数来生成大量的随机数,而不必将它们全部存储在内存中。

除了使用yield语句来生成值,生成器函数还可以接受参数,用于控制生成的值。例如,我们可以编写一个生成斐波那契数列的生成器函数,如下所示:

def fibonacci(n):
    a, b = 0, 1
    for i in range(n):
        yield a
        a, b = b, a + b

generator = fibonacci(10)

for i in generator:
    print(i)

在上面的代码中,我们定义了一个名为fibonacci()的生成器函数,用于生成斐波那契数列的前n个数。每次执行到yield语句时,函数会暂停执行,并将生成的值返回给调用方。然后我们创建了一个fibonacci(10)函数的生成器对象,并将它用于for循环中进行迭代,打印了前10个斐波那契数列的数。

需要注意的是,生成器函数是一次性的,即在生成器对象上进行一次遍历后,它的状态会被改变,再次进行遍历时会从上一次结束的位置开始。如果需要再次进行遍历,需要重新创建一个新的生成器对象。

9.8.8 反向迭代:reversed迭代器

在Python中,可以使用reversed()函数获取一个反向迭代器对象,用于反向遍历一个序列。

reversed()函数接受一个序列作为参数,返回一个迭代器对象,该迭代器对象会反向遍历序列中的元素。与正向迭代器不同,反向迭代器是从序列的末尾开始遍历的,每次调用next()方法时,会返回序列中的前一个元素,直到遍历完整个序列。

以下是一个使用reversed()函数进行反向遍历的示例:

lst = [1, 2, 3, 4, 5]

for i in reversed(lst):
    print(i)

在上面的代码中,我们定义了一个列表对象lst,然后使用reversed()函数获取一个反向迭代器对象,并在for循环中使用它进行反向遍历。输出结果为:

5
4
3
2
1

需要注意的是,由于reversed()函数返回的是一个迭代器对象,因此它只能遍历一次。如果需要多次遍历,需要重新获取一个新的迭代器对象。

此外,除了使用reversed()函数外,还可以使用[::-1]切片操作对序列进行反转。例如:

lst = [1, 2, 3, 4, 5]

for i in lst[::-1]:
    print(i)

这段代码与上述代码的输出结果相同,都是反向遍历列表中的元素。但是,使用切片操作会创建一个新的反转后的列表,而使用reversed()函数则不会创建新的列表,因此在处理大量数据时,使用reversed()函数可以节省内存空间。

9.8.9 生成器表达式

生成器表达式是一种使用简单语法快速创建生成器的方法。它类似于列表推导式,但是使用圆括号()而不是方括号[]来包含表达式。

与列表推导式不同的是,生成器表达式不会立即生成所有的值,而是在需要时逐个生成。这使得生成器表达式可以处理大量数据,而不会占用过多的内存空间。

以下是一个生成器表达式的示例:

generator = (x * x for x in range(10))

for i in generator:
    print(i)

在上面的代码中,我们使用生成器表达式(x * x for x in range(10))创建了一个生成器对象generator,用于生成数字0到9的平方。然后我们使用for循环对生成器对象进行迭代,并打印每个生成的值。

需要注意的是,生成器表达式只能用于生成一次序列,即在使用完生成器对象后,它就不能再次使用。如果需要重新生成序列,需要重新创建一个新的生成器对象。

另外,生成器表达式可以包含复杂的表达式和条件语句,用于生成满足特定条件的值。例如:

generator = (x * x for x in range(10) if x % 2 == 0)

for i in generator:
    print(i)

在上面的代码中,我们使用生成器表达式(x * x for x in range(10) if x % 2 == 0)创建了一个生成器对象generator,用于生成数字0到9中的偶数的平方。然后我们使用for循环对生成器对象进行迭代,并打印每个生成的值。

生成器表达式是一种简单而强大的工具,可以帮助我们快速创建生成器并处理大量数据。它可以与其他Python语言特性(如条件语句和函数)结合使用,以实现复杂的数据处理任务。

9.8.10 range可迭代对象

在Python中,range()函数返回的是一个可迭代对象,用于生成指定范围内的整数序列。可以使用for循环或其他可迭代对象的方式进行遍历。例如:

for i in range(5):
    print(i)

这段代码会输出数字0到4,因为range(5)返回的是一个包含5个元素的可迭代对象,用于生成数字0到4的序列。

9.8.11map迭代器和itertools.starmap迭代器

map()函数返回的是一个迭代器,用于对序列中的每个元素应用指定的函数,返回一个新的序列。例如:

lst = [1, 2, 3, 4, 5]
result = map(lambda x: x * x, lst)

for i in result:
    print(i)

这段代码会输出数字1到25,因为map()函数将序列lst中的每个元素都平方,并返回一个新的序列,然后使用for循环对返回的迭代器进行遍历。

itertools.starmap()函数与map()函数类似,但是它将序列中的每个元素作为参数传递给指定的函数,而不是将整个序列作为参数传递。例如:

import itertools

lst = [(1, 2), (3, 4), (5, 6)]
result = itertools.starmap(lambda x, y: x + y, lst)

for i in result:
    print(i)

这段代码会输出数字3、7和11,因为itertools.starmap()函数将序列lst中的每个元组中的元素作为参数传递给指定的函数lambda x, y: x + y,并返回一个新的序列。然后使用for循环对返回的迭代器进行遍历。

9.8.12 filter迭代器和itertools.filterfalse迭代器

filter()函数返回的是一个迭代器,用于过滤序列中满足指定条件的元素,返回一个新的序列。例如:

lst = [1, 2, 3, 4, 5]
result = filter(lambda x: x % 2 == 0, lst)

for i in result:
    print(i)

这段代码会输出数字2和4,因为filter()函数将序列lst中的每个元素都判断是否为偶数,并返回一个新的序列,只包含偶数元素。然后使用for循环对返回的迭代器进行遍历。

itertools.filterfalse()函数与filter()函数类似,但是它返回的是序列中不满足指定条件的元素,而不是满足条件的元素。例如:

import itertools

lst = [1, 2, 3, 4, 5]
result = itertools.filterfalse(lambda x: x % 2 == 0, lst)

for i in result:
    print(i)

这段代码会输出数字1、3和5,因为itertools.filterfalse()函数将序列lst中的每个元素都判断是否为偶数,并返回一个新的序列,只包含不为偶数的元素。然后使用for循环对返回的迭代器进行遍历。

9.8.13 zip迭代器和itertools.zip_longest迭代器

zip()函数返回的是一个迭代器,用于将多个序列中的对应元素组合成元组,生成一个新的序列。例如:

lst1 = [1, 2, 3]
lst2 = ['a', 'b', 'c']
result = zip(lst1, lst2)

for i in result:
    print(i)

这段代码会输出(1, 'a')(2, 'b')(3, 'c'),因为zip()函数将序列lst1lst2中对应位置的元素分别组合成元组,并返回一个新的序列。然后使用for循环对返回的迭代器进行遍历。

itertools.zip_longest()函数与zip()函数类似,但是如果序列长度不同,它会用指定的值填充较短序列中缺失的元素。例如:

import itertools

lst1 = [1, 2, 3]
lst2 = ['a', 'b']
result = itertools.zip_longest(lst1, lst2, fillvalue='N/A')

for i in result:
    print(i)

这段代码会输出(1, 'a')(2, 'b')(3, 'N/A'),因为itertools.zip_longest()函数将序列lst1lst2中对应位置的元素分别组合成元组,并返回一个新的序列。由于lst2中只有两个元素,缺少一个元素,因此使用指定的值'N/A'填充缺失的元素。然后使用for循环对返回的迭代器进行遍历。

9.8.14 enumberate迭代器

在Python中,enumerate()函数返回一个可迭代对象,用于将一个序列中的元素和对应的索引组成元组,然后生成一个新的序列。这个新的序列中的每个元素都是一个包含两个值的元组,第一个值是元素的索引,第二个值是元素的值。

以下是一个使用enumerate()函数的示例:

lst = ['a', 'b', 'c', 'd']

for i, value in enumerate(lst):
    print(i, value)

在上面的代码中,我们使用enumerate()函数对列表对象lst进行迭代,并将每个元素和对应的索引组成元组。然后使用for循环对返回的可迭代对象进行遍历,并将每个元组中的第一个值(即索引)赋值给变量i,第二个值(即元素的值)赋值给变量value。最后打印出每个元素的索引和值。

输出结果为:

0 a
1 b
2 c
3 d

需要注意的是,enumerate()函数默认从0开始给元素编号,可以通过指定start参数来指定起始编号。例如:

lst = ['a', 'b', 'c', 'd']

for i, value in enumerate(lst, start=1):
    print(i, value)

这段代码会输出数字1到4,因为我们指定了start=1,从1开始进行编号。

使用enumerate()函数可以方便地遍历序列中的元素和对应的索引,特别是在需要对序列中的元素进行操作时,能够更加方便地定位元素的位置。

9.8.15 无穷序列迭代器itertools.count,cycle和repeat

  1. itertools.count(start=0, step=1)

itertools.count()函数返回一个无限序列的迭代器,用于生成连续的数字。可以指定起始值start和步长step,默认值分别为0和1。

import itertools

# 输出前10个偶数
for i in itertools.count(start=0, step=2):
    if i > 18:
        break
    print(i)

输出结果为:

0
2
4
6
8
10
12
14
16
18
  1. itertools.cycle(iterable)

itertools.cycle()函数返回一个无限序列的迭代器,用于循环序列中的元素。可以传入一个可迭代对象作为参数,例如一个列表或元组。

import itertools

lst = ['a', 'b', 'c']
count = 0

# 输出前10个元素
for value in itertools.cycle(lst):
    if count > 9:
        break
    print(value)
    count += 1

输出结果为:

a
b
c
a
b
c
a
b
c
a
  1. itertools.repeat(object, times=None)

itertools.repeat()函数返回一个无限序列的迭代器,用于重复一个值。可以传入一个对象作为参数,以及一个可选的重复次数times,默认为None,表示重复无限次。

import itertools

# 重复10次字符串"hello"
for value in itertools.repeat("hello", times=10):
    print(value)

输出结果为:

hello
hello
hello
hello
hello
hello
hello
hello
hello
hello

这些无穷序列迭代器可以和zip()迭代器一起使用,方便地对多个序列进行迭代。

以下是一个使用zip()迭代器搭配itertools.count()函数的示例:

import itertools

lst1 = ['a', 'b', 'c']
lst2 = ['x', 'y', 'z']

# 使用zip迭代器和count函数对两个列表进行迭代
for i, (value1, value2) in zip(itertools.count(start=1), zip(lst1, lst2)):
    print(i, value1, value2)

在上面的代码中,我们使用itertools.count()函数生成连续的数字,用于对两个列表进行编号。然后使用zip()迭代器将两个列表打包成一个可迭代对象,对其进行迭代,并使用for循环和enumerate()函数对返回的可迭代对象进行遍历,输出编号和两个列表中对应位置的元素。

输出结果为:

1 a x
2 b y
3 c z

这样的代码可以用于处理多个序列的数据,例如将多个列表中的数据打包成元组,或者对多个数据进行统计分析等。

9.8.16 累计迭代器itertools.accumulate

在Python中,itertools模块还提供了一个用于累计计算的迭代器accumulate()。它返回一个无限序列的迭代器,用于对序列中的元素进行累计计算,例如求和、求积、计算最大值、最小值等。

accumulate()函数有两个参数:

  • iterable:要进行累计计算的可迭代对象;
  • func:一个二元函数,用于对相邻的两个元素进行计算。如果省略该参数,则默认使用加法运算。

以下是一个使用accumulate()函数的示例:

import itertools

# 对列表中的元素进行累加计算
lst = [1, 2, 3, 4, 5]
result = itertools.accumulate(lst)
print(list(result))  # 输出结果为 [1, 3, 6, 10, 15]

在上面的代码中,我们使用itertools.accumulate()函数对列表lst中的元素进行累加计算。然后将返回的迭代器转换成列表并打印出来,可以看到结果为 [1, 3, 6, 10, 15],这是列表中每个元素的前缀和。

除了加法,还可以使用其他的二元函数对相邻的两个元素进行计算。例如:

  • operator.mul:乘法运算;
  • operator.sub:减法运算;
  • max:计算相邻两个元素的最大值;
  • min:计算相邻两个元素的最小值。

例如,以下代码使用operator.mul函数对列表中的元素进行累积计算,即求列表元素的乘积:

import itertools
import operator

# 对列表中的元素进行累积计算
lst = [1, 2, 3, 4, 5]
result = itertools.accumulate(lst, operator.mul)
print(list(result))  # 输出结果为 [1, 2, 6, 24, 120]

输出结果为:

[1, 2, 6, 24, 120]

itertools.accumulate()函数返回的是一个迭代器,可以和其他迭代器一起使用,例如zip()迭代器和enumerate()函数。以下是一个示例:

import itertools

lst1 = [1, 2, 3, 4, 5]
lst2 = [10, 20, 30, 40, 50]

# 使用zip迭代器和accumulate函数对两个列表进行累加计算
result = itertools.accumulate(zip(lst1, lst2), lambda x, y: (x[0] + y[0], x[1] + y[1]))
for i, value in enumerate(result):
    print(i, value)

在上面的代码中,我们使用zip()迭代器将两个列表打包成一个可迭代对象,对其进行累加计算,并使用enumerate()函数对返回的迭代器进行遍历,输出编号和计算结果。

输出结果为:

0 (1, 10)
1 (3, 30)
2 (6, 60)
3 (10, 100)
4 (15, 150)

这样的代码可以用于对多个序列的数据进行累加计算,例如计算多个时间序列的累积值,或者对多个指标进行统计分析等。

9.8.17 级联迭代器 itertools.chain

itertools.chain()函数是一个级联迭代器,它可以将多个可迭代对象级联在一起,返回一个新的迭代器。对于每一个可迭代对象,chain()函数会将其元素依次添加到结果迭代器中,直到所有可迭代对象的元素都添加完毕。

chain()函数有一个参数:

  • iterables:要级联的多个可迭代对象,可以是任意个数。

以下是一个使用chain()函数的示例:

import itertools

lst1 = [1, 2, 3]
lst2 = ['a', 'b', 'c']
lst3 = ['x', 'y', 'z']

# 将三个列表级联在一起
result = itertools.chain(lst1, lst2, lst3)
print(list(result))  # 输出结果为 [1, 2, 3, 'a', 'b', 'c', 'x', 'y', 'z']

在上面的代码中,我们使用itertools.chain()函数将三个列表lst1lst2lst3级联在一起,并将返回的迭代器转换成列表并打印出来,可以看到结果为 [1, 2, 3, 'a', 'b', 'c', 'x', 'y', 'z'],这是三个列表中的所有元素级联在一起的结果。

chain()函数返回的是一个迭代器,也可以和其他迭代器一起使用,例如zip()迭代器和enumerate()函数。以下是一个示例:

import itertools

lst1 = [1, 2, 3]
lst2 = ['a', 'b', 'c']
lst3 = ['x', 'y', 'z']

# 将三个列表级联在一起,并使用zip迭代器和enumerate函数进行迭代
result = itertools.chain(lst1, lst2, lst3)
for i, value in enumerate(zip(result, result)):
    print(i, value)

在上面的代码中,我们使用itertools.chain()函数将三个列表级联在一起,并使用zip()迭代器和enumerate()函数对返回的迭代器进行遍历,输出编号和每个元素对。

输出结果为:

0 (1, 'a')
1 (2, 'b')
2 (3, 'c')

这样的代码可以用于对多个序列的数据进行级联处理,例如将多个文件中的数据级联在一起进行处理,或者对多个数据集进行联合分析等。

9.8.18 选择压缩迭代器itertools.cmpress

  1. itertools.compress(data, selectors):选择压缩迭代器。返回一个迭代器,用于将两个可迭代对象dataselectors压缩在一起。data是一个可迭代对象,用于提供数据;selectors是一个可迭代对象,用于提供一个布尔值序列,用于选择哪些数据要被保留。例如:

    import itertools
    
    data = ['a', 'b', 'c', 'd', 'e']
    selectors = [True, False, True, False, True]
    
    result = itertools.compress(data, selectors)
    print(list(result))  # 输出结果为 ['a', 'c', 'e']
    ```
    
    

9.8.19 截取迭代器itertools.dropwhile和takewhile

  1. itertools.dropwhile(predicate, iterable)itertools.takewhile(predicate, iterable):截取迭代器。这两个函数都返回一个迭代器,用于从一个可迭代对象中截取一部分元素。takewhile()函数会一直取元素,直到谓词函数predicate返回Falsedropwhile()函数则会一直跳过元素,直到谓词函数predicate返回False,然后返回剩下的元素。例如:

    import itertools
    
    lst = [1, 2, 3, 4, 5, 6, 7]
    
    result = itertools.takewhile(lambda x: x < 4, lst)
    print(list(result))  # 输出结果为 [1, 2, 3]
    
    result = itertools.dropwhile(lambda x: x < 4, lst)
    print(list(result))  # 输出结果为 [4, 5, 6, 7]
    ```
    
    

9.8.20 切片迭代器itertools.islice

  1. itertools.islice(iterable, start, stop[, step]):切片迭代器。返回一个迭代器,用于从一个可迭代对象中的指定位置开始取出一定数量的元素。start参数表示起始位置,stop参数表示结束位置,step参数表示步长。例如:

    import itertools
    
    lst = [1, 2, 3, 4, 5, 6, 7]
    
    result = itertools.islice(lst, 2, 5)
    print(list(result))  # 输出结果为 [3, 4, 5]
    ```
    
    

9.8.21 分组迭代器itertools.groupby

  1. itertools.groupby(iterable, key=None):分组迭代器。返回一个迭代器,用于将一个可迭代对象中相邻的重复元素分组。key参数是一个函数,用于指定分组的规则。例如:

    import itertools
    
    lst = [1, 1, 2, 2, 3, 3, 3, 4, 4]
    
    result = itertools.groupby(lst)
    for key, group in result:
        print(key, list(group))
    ```
    
    输出结果为:
    
    

    1 [1, 1]
    2 [2, 2]
    3 [3, 3, 3]
    4 [4, 4]

    
    

9.8.22 返回多个迭代器itertools.tee

  1. itertools.tee(iterable, n=2):返回多个迭代器。返回一个元组,其中包含n个迭代器,每个迭代器都可以独立地迭代原始的可迭代对象。例如:

    import itertools
    
    lst = [1, 2, 3, 4, 5]
    
    result1, result2 = itertools.tee(lst)
    
    print(list(result1))  # 输出结果为 [1, 2, 3, 4, 5]
    print(list(result2))  # 输出结果为 [1, 2, 3, 4, 5]
    ```
    
    

9.8.23 组合迭代器itertools.combinations和combinations_with_replacement

  1. itertools.combinations(iterable, r)itertools.combinations_with_replacement(iterable, r):组合迭代器。这两个函数都返回一个迭代器,用于生成所有长度为r的组合。combinations()函数不允许重复,而combinations_with_replacement()函数允许重复。例如:

    import itertools
    
    lst = [1, 2, 3]
    
    result = itertools.combinations(lst, 2)
    print(list(result))  # 输出结果为 [(1, 2), (1, 3), (2, 3)]
    
    result = itertools.combinations_with_replacement(lst, 2)
    print(list(result))  # 输出结果为 [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]
    ```
    
    

9.8.24 排列迭代器itertools.permutations

  1. itertools.permutations(iterable, r=None):排列迭代器。返回一个迭代器,用于生成所有长度为r的排列。如果不指定r参数,则返回所有元素的全排列。例如:

    import itertools
    
    lst = [1, 2, 3]
    
    result = itertools.permutations(lst)
    print(list(result))  # 输出结果为 [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
    
    result = itertools.permutations(lst, 2)
    print(list(result))  # 输出结果为 [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
    ```
    
    

9.8.25 笛卡尔积迭代器itertools.product

  1. itertools.product(*iterables, repeat=1):笛卡尔积迭代器。返回一个迭代器,用于生成多个可迭代对象的笛卡尔积。repeat参数表示可迭代对象可以重复使用的次数。例如:

    import itertools
    
    lst1 = [1, 2]
    lst2 = ['a', 'b']
    
    result = itertools.product(lst1, lst2)
    print(list(result))  # 输出结果为 [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
    
    result = itertools.product(lst1, lst2, repeat=2)
    print(list(result))  # 输出结果为 [(1, 'a', 1, 'a'), (1, 'a', 1, 'b'), (1, 'a', 2, 'a'), (1, 'a', 2, 'b'), (1, 'b', 1, 'a'), (1, 'b', 1, 'b'), (1, 'b', 2, 'a'), (1, 'b', 2, 'b'), (2, 'a', 1, 'a'), (2, 'a', 1, 'b'), (2, 'a', 2, 'a'), (2, 'a', 2, 'b'), (2, 'b', 1, 'a'), (2, 'b', 1, 'b'), (2, 'b', 2, 'a'), (2, 'b', 2, 'b')]
    ```
    
    

9.9 自定义类应用举例

Python语言,标准库和第三方库中定义了大量的类,类是Python语言的主要数据结构.用户也可以通过自定义类创建和使用新的数据结构.

9.9.1 Color类

Color类封装使用RGB颜色模型表示颜色及相关功能.设计思路如下

  1. 定义带3个0到255的整数参数r,g,b的构造函数,用于初始化对应于红绿蓝3中颜色分类的实例属性_r,_g,_b
  2. 通过装饰器@property定义3个可以作为属性访问的实例属性_r,_g,_b
  3. 定义用于计算颜色亮度的方法luminance(self):Y = 0.299r+0.578g+0.114b
  4. 定义用于转换为灰度颜色亮度的方法toGray(self)
  5. 定义用于比较两种颜色兼容性的方法isCompatible(self,c).颜色兼容性指在以一种颜色为背景时另一种颜色的可阅读性.一般而言,前景色和背景色的亮度差至少应该是128.白纸黑字的亮度差为255
class Color:
    """表示RGB模型的类"""
    def __init__(self, r=0, g=0, b=0):
        """构造函数"""
        self._r = r  #Red红色分量
        self._g = g  #Green绿色分量
        self._b = b  #Blue蓝色分量
    @property
    def r(self):
        return self._r
    @property
    def g(self):
        return self._g
    @property
    def b(self):
        return self._b
    def luminance(self):
        """计算并返回颜色的亮度"""
        return .299*self._r + .587*self._g + .114*self._b
    def toGray(self):
        """转换为灰度颜色"""
        y = int(round(self.luminance()))
        return Color(y, y, y)
    def isCompatible(self, c):
        """比较前景色和背景色是否匹配"""
        return abs(self.luminance() - c.luminance()) >= 128.0
    def __str__(self):
        """重载方法,输出:(r, g, b)"""        
        return '({},{},{})'.format(self._r,self._g,self._b)
#常用颜色
WHITE      = Color(255, 255, 255)
BLACK      = Color(  0,   0,   0)
RED        = Color(255,   0,   0)
GREEN      = Color(  0, 255,   0)
BLUE       = Color(  0,   0, 255)
CYAN       = Color(  0, 255, 255)
MAGENTA    = Color(255,   0, 255)
YELLOW     = Color(255, 255,   0)
#测试代码
if __name__ == '__main__':    
    c = Color(255, 200, 0) #ORANGE黄色
    print('颜色字符串:{}'.format(c)) #输出颜色字符串
    print('颜色分量:r={},g={},b={}'.format(c.r, c.g, c.b)) #输出个颜色分量
    print('颜色亮度:{}'.format(c.luminance())) #输出颜色亮度
    print('转换为幅度颜色:{}'.format(c.toGray())) #输出转换后的灰度颜色
print('{}和{}是否匹配:{}'.format(c,RED,c.isCompatible(RED))) #比较与红色是否匹配

在这里插入图片描述

9.9.2 Histogram类

Histogram类封装直方图(包括数据及基本统计功能),设计思路如下:

  1. 定义带一个整数参数n的构造函数,用于初始化存储数据的列表,列表长度为n,列表各元素的初始值为0
  2. 定义实例对象方法addDataPoint(self,i)用于增加一个数据点
  3. 定义用与计算数据点个数之和,平均值,最大值,最小值得实例对象方法,即count(),mean(),max(),min()
  4. 定义用于绘制简单直方图的实例对象方法draw()
import random
import math
class Stat:
    def __init__(self, n):
        self._data = []
        for i in range(n):
            self._data.append(0)
    def addDataPoint(self, i):
        """增加数据点"""
        self._data[i] += 1
    def count(self):
        """计算数据点个数之和(统计数据点个数)"""
        return sum(self._data)
    def mean(self):
        """计算各数据点个数的平均值"""
        return sum(self._data)/len(self._data)
    def max(self):
        """计算各数据点个数的最大值"""
        return max(self._data)
    def min(self):
        """计算各数据点个数的最小值"""
        return min(self._data)
    def draw(self):
        """绘制简易直方图"""
        for i in self._data:
            print('#'* i)
#测试代码
if __name__ == '__main__':    
    #随机生成100个的0到9的数
    st = Stat(10)
    for i in range(100):
        score = random.randrange(0,10)
        st.addDataPoint(math.floor(score))
    print('数据点个数:{}'.format(st.count()))
    print('数据点个数的平均值:{}'.format(st.mean()))
    print('数据点个数的最大值:{}'.format(st.max()))
    print('数据点个数的最小值:{}'.format(st.min()))
    st.draw()       #绘制简易直方图

在这里插入图片描述

c = Color(255, 200, 0) #ORANGE黄色
print(‘颜色字符串:{}’.format©) #输出颜色字符串
print(‘颜色分量:r={},g={},b={}’.format(c.r, c.g, c.b)) #输出个颜色分量
print(‘颜色亮度:{}’.format(c.luminance())) #输出颜色亮度
print(‘转换为幅度颜色:{}’.format(c.toGray())) #输出转换后的灰度颜色
print(‘{}和{}是否匹配:{}’.format(c,RED,c.isCompatible(RED))) #比较与红色是否匹配



## 9.9.2 Histogram类

Histogram类封装直方图(包括数据及基本统计功能),设计思路如下:

1. 定义带一个整数参数n的构造函数,用于初始化存储数据的列表,列表长度为n,列表各元素的初始值为0
2. 定义实例对象方法addDataPoint(self,i)用于增加一个数据点
3. 定义用与计算数据点个数之和,平均值,最大值,最小值得实例对象方法,即count(),mean(),max(),min()
4. 定义用于绘制简单直方图的实例对象方法draw()

```python
import random
import math
class Stat:
    def __init__(self, n):
        self._data = []
        for i in range(n):
            self._data.append(0)
    def addDataPoint(self, i):
        """增加数据点"""
        self._data[i] += 1
    def count(self):
        """计算数据点个数之和(统计数据点个数)"""
        return sum(self._data)
    def mean(self):
        """计算各数据点个数的平均值"""
        return sum(self._data)/len(self._data)
    def max(self):
        """计算各数据点个数的最大值"""
        return max(self._data)
    def min(self):
        """计算各数据点个数的最小值"""
        return min(self._data)
    def draw(self):
        """绘制简易直方图"""
        for i in self._data:
            print('#'* i)
#测试代码
if __name__ == '__main__':    
    #随机生成100个的0到9的数
    st = Stat(10)
    for i in range(100):
        score = random.randrange(0,10)
        st.addDataPoint(math.floor(score))
    print('数据点个数:{}'.format(st.count()))
    print('数据点个数的平均值:{}'.format(st.mean()))
    print('数据点个数的最大值:{}'.format(st.max()))
    print('数据点个数的最小值:{}'.format(st.min()))
    st.draw()       #绘制简易直方图

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值