文章目录
- 9.1 面向对象概念
- 9.3 属性
- 9.4 方法
- 9.5 继承
- 9.6 对象的特殊方法
- 9.7 对象的引用,浅拷贝和深拷贝
- 9.8 可迭代对象:迭代器和生成器
- 9.8.1 可迭代对象
- 9.8.2 迭代器
- 9.8.5 可迭代对象的迭代:iter()函数和next()函数
- 9.8.5 for,while语句进行可迭代对象的迭代
- 9.8.6自定义可迭代对象和迭代器
- 9.8.7 生成器函数
- 9.8.8 反向迭代:reversed迭代器
- 9.8.9 生成器表达式
- 9.8.10 range可迭代对象
- 9.8.11`map`迭代器和`itertools.starmap`迭代器
- 9.8.12 `filter`迭代器和`itertools.filterfalse`迭代器
- 9.8.13 `zip`迭代器和`itertools.zip_longest`迭代器
- 9.8.14 enumberate迭代器
- 9.8.15 无穷序列迭代器itertools.count,cycle和repeat
- 9.8.16 累计迭代器itertools.accumulate
- 9.8.17 级联迭代器 itertools.chain
- 9.8.18 选择压缩迭代器itertools.cmpress
- 9.8.19 截取迭代器itertools.dropwhile和takewhile
- 9.8.20 切片迭代器itertools.islice
- 9.8.21 分组迭代器itertools.groupby
- 9.8.22 返回多个迭代器itertools.tee
- 9.8.23 组合迭代器itertools.combinations和combinations_with_replacement
- 9.8.24 排列迭代器itertools.permutations
- 9.8.25 笛卡尔积迭代器itertools.product
- 9.9 自定义类应用举例
- 9.9.1 Color类
类是一种数据结构,可包含数据成员和函数成员.在程序设计中可以定义类,并创建和使用其实例.
9.1 面向对象概念
9.1.1 对象的定义
在计算机科学中,对象是指一种数据结构,它包含了一些数据和与这些数据相关的操作。对象的数据被称为其状态,而对象的操作被称为其方法。对象可以看作是一种封装了数据和方法的实体。
对象是面向对象编程的基本概念之一。在面向对象编程中,对象是程序的基本单元,程序的行为由一组相互作用的对象之间的交互所决定。对象通常被创建、使用和销毁,并且它们之间可以进行通信和协作,以实现程序的功能。
每个对象都有一个类型,它定义了对象具有的属性和方法。类型可以看作是对象的模板或蓝图,它描述了对象的基本结构和行为。在面向对象编程中,通过定义不同的类型,可以创建多个具有不同属性和行为的对象。
9.1.2 封装,继承,多态
封装、继承和多态是面向对象编程中的三个基本概念,它们是实现面向对象编程的重要手段。
-
封装(Encapsulation):封装是面向对象编程的核心概念之一。它指的是将数据和行为封装在一个单独的单元中,即对象。对象的内部状态和行为对外部是不可见的,只有通过对象的接口才能访问和操作对象。封装可以提高程序的安全性和可维护性,同时也可以隐藏复杂性,使程序更易于理解和使用。
-
继承(Inheritance):继承是一种从已有类派生出新类的机制。通过继承,新类可以继承已有类的属性和方法,并可以添加新的属性和方法,从而扩展和定制已有类的功能。继承可以提高代码的重用性和可扩展性,同时也可以使代码更易于维护和理解。
-
多态(Polymorphism):多态是指同一种操作可以作用于不同的对象,并能够产生不同的结果。在面向对象编程中,多态可以通过方法重载和方法重写来实现。方法重载是指在同一个类中定义多个同名但参数不同的方法,而方法重写是指子类重新定义父类的方法。多态可以提高程序的灵活性和扩展性,同时也可以使程序更易于理解和使用。
这三个概念是面向对象编程中非常重要的,它们相互关联,相互依赖,共同构成了面向对象编程的核心思想。
9.1.3 类对象和实例对象
在面向对象编程中,类对象和实例对象是两个重要的概念。
-
类对象(Class object):类对象是用来创建实例对象的模板。在Python中,每个类都是一个对象,这个对象包含了类的名称、属性和方法等信息。通过类对象可以访问类的属性和方法,也可以用来创建实例对象。
-
实例对象(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__()
中初始化了实例属性name
和age
。然后,我们创建了一个名为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中,私有属性和公有属性是面向对象编程中的一个重要概念。
- 私有属性(Private attribute):私有属性是指只能在类的内部访问的属性,外部无法直接访问。在Python中,可以通过在属性名前面加上两个下划线"__"来定义私有属性。例如:
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age
在上面的例子中,我们定义了两个私有属性__name
和__age
,它们只能在类的内部访问,外部无法直接访问。如果在外部使用点操作符来访问这些私有属性,会导致AttributeError异常。
- 公有属性(Public attribute):公有属性是指可以在类的内部和外部都可以访问的属性。在Python中,没有特殊的语法来定义公有属性,所有没有定义为私有属性的属性都是公有属性。例如:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
在上面的例子中,我们定义了两个公有属性name
和age
,它们可以在类的内部和外部都可以访问。
需要注意的是,在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
的类,它有两个属性width
和height
,以及一个@property
装饰器修饰的方法area
。这个方法计算并返回矩形的面积,但是它被定义为只读属性,因此不能被修改。
使用@property
装饰器可以使方法看起来像一个属性,从而使代码更加清晰和易读。同时,它还可以提供一种简单的方式来控制属性的访问和修改,从而增强程序的安全性和可维护性。
在Python中,getter
、setter
和deleter
方法是用于访问和修改类属性的方法,它们通常与@property
装饰器一起使用。
- 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
的类,它有两个属性width
和height
,以及两个使用@property
装饰器修饰的方法width
和height
。这两个方法可以用于获取属性的值,但是不能用于修改属性的值。
- 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
。这个方法可以用于设置属性的值。
- 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中,对象的方法可以分为实例方法、静态方法和类方法。每种方法类型都有其特定的调用方式和用途。
- 对象实例方法
对象实例方法是最常见的一种方法类型,它可以通过类的实例来调用。对象实例方法的第一个参数通常是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
,用于打印人的姓名和年龄。这个方法是一个对象实例方法,它可以通过类的实例来调用。
- 静态方法
静态方法是一种不需要访问对象状态的方法,它可以通过类来调用。静态方法通常用于执行通用功能,例如字符串格式化或计算数学函数。静态方法使用@staticmethod
装饰器来定义,它不需要self
参数。例如:
class Math:
@staticmethod
def add(x, y):
return x + y
result = Math.add(3, 5)
print(result) # 输出 8
在上面的例子中,我们定义了一个名为Math
的类,它有一个静态方法add
,用于计算两个数的和。这个方法是一个静态方法,它可以通过类来调用。
- 类方法
类方法是一种可以访问类状态的方法,它也可以通过类来调用。类方法通常用于创建或操作类级别的状态,例如计算类的大小或创建单例对象。类方法使用@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__()
。这两个方法在创建对象时都会被调用,但它们的作用不同。
__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__()
方法进行初始化。
__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
。当我们删除p1
和p2
时,Python会自动调用__del__
()方法来释放对象的资源。
需要注意的是,__del__
()方法并不是完全可靠的,因为它的调用时间是不确定的。当对象的引用计数为0时,Python会自动调用__del__
()方法,但是在某些情况下,对象可能永远不会被销毁,例如循环引用的情况。因此,在编写Python程序时,我们应该尽量避免使用__del__
()方法,而是采用其他方式来释放资源。
9.4.4 私有方法与公有方法
在Python中,方法前面添加双下划线__
的方法被称为私有方法,而没有添加双下划线的方法被称为公有方法。私有方法和公有方法的主要区别在于它们的访问权限。
- 私有方法
私有方法只能在类的内部被访问,无法在类的外部直接访问。私有方法通常用于实现类的内部细节,防止外部对象直接访问和修改类的状态。在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中存在私有方法,但是在实际编程中,我们往往不会强制限制外部对象访问和修改类的状态,而是通过命名约定来表示某个方法或属性是私有的,例如在方法名前添加单下划线_
。
- 公有方法
公有方法可以在类的内部和外部被访问,它们通常用于实现类的公有接口,提供给外部对象使用。在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
的类,它有两个方法add
和add_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
的类,它有两个同名方法add
和add_numbers
,它们的参数个数和类型不同。在add
方法中,我们传递了两个参数a
和b
,计算它们的和并返回。在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个类A
、B
、C
、D
,其中B
和C
继承自A
,D
继承自B
和C
。当我们使用mro()
方法查看D
类的继承层次关系时,Python会依次输出D
、B
、C
、A
和object
,表示方法解析顺序为D
、B
、C
、A
和object
。这意味着,当我们调用D
类的方法时,Python会首先在D
类中查找,如果找不到就依次在B
、C
、A
和object
类中查找,直到找到或者查找完所有类为止。
需要注意的是,mro()
方法只能在新式类中使用,不能在经典类中使用。在Python 2.x中,默认情况下定义的类都是经典类,在Python 3.x中默认情况下定义的类都是新式类。如果需要在Python 2.x中使用新式类,可以在类定义时继承自object
类。例如:
class A(object): # 继承自 object 类
pass
9.5.3 类成员的继承和重写
类成员(属性和方法)的继承和重写是面向对象编程中的重要概念。在Python中,派生类可以继承其父类的属性和方法,并且可以重写父类的方法来实现不同的行为。下面对类成员的继承和重写进行详细说明。
- 属性的继承
派生类可以继承其父类的属性,这意味着派生类可以直接访问父类的属性,而不需要重新定义它们。例如:
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
的父类,它有两个属性name
和age
。然后我们定义了一个名为Student
的派生类,它继承自Person
类。在创建Student
对象时,我们可以直接访问父类的属性name
和age
,而不需要重新定义它们。
- 方法的继承
派生类可以继承其父类的方法,这意味着派生类可以直接调用父类的方法,而不需要重新定义它们。例如:
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
方法,而不需要重新定义它。
- 方法的重写
派生类可以重写其父类的方法,从而实现不同的行为。在派生类中,如果定义了与父类同名的方法,则该方法会覆盖父类的方法。例如:
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
类,它包含x
和y
两个属性,表示向量的坐标。我们重载了加法运算符+
的特殊方法__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
类,表示一个人物对象,包含name
和age
两个属性。我们使用@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提供了两种方式来实现对象的浅拷贝:
- 使用切片操作符
[:]
或者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}
- 使用
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提供了两种方式来实现对象的深拷贝:
- 使用
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
坐标。最后,我们分别输出原对象和新对象中的点对象的坐标,验证它们是否被修改。
- 对列表、集合、字典等可变对象进行深拷贝时,可以使用切片操作符
[:]
或者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()
函数会抛出TypeError
或StopIteration
异常。
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()
函数将序列lst1
和lst2
中对应位置的元素分别组合成元组,并返回一个新的序列。然后使用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()
函数将序列lst1
和lst2
中对应位置的元素分别组合成元组,并返回一个新的序列。由于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
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
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
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()
函数将三个列表lst1
、lst2
和lst3
级联在一起,并将返回的迭代器转换成列表并打印出来,可以看到结果为 [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
-
itertools.compress(data, selectors)
:选择压缩迭代器。返回一个迭代器,用于将两个可迭代对象data
和selectors
压缩在一起。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
-
itertools.dropwhile(predicate, iterable)
和itertools.takewhile(predicate, iterable)
:截取迭代器。这两个函数都返回一个迭代器,用于从一个可迭代对象中截取一部分元素。takewhile()
函数会一直取元素,直到谓词函数predicate
返回False
;dropwhile()
函数则会一直跳过元素,直到谓词函数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
-
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
-
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
-
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
-
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
-
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
-
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颜色模型表示颜色及相关功能.设计思路如下
- 定义带3个0到255的整数参数r,g,b的构造函数,用于初始化对应于红绿蓝3中颜色分类的实例属性
_r,_g,_b
- 通过装饰器@property定义3个可以作为属性访问的实例属性
_r,_g,_b
- 定义用于计算颜色亮度的方法luminance(self):Y = 0.299r+0.578g+0.114b
- 定义用于转换为灰度颜色亮度的方法toGray(self)
- 定义用于比较两种颜色兼容性的方法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类封装直方图(包括数据及基本统计功能),设计思路如下:
- 定义带一个整数参数n的构造函数,用于初始化存储数据的列表,列表长度为n,列表各元素的初始值为0
- 定义实例对象方法addDataPoint(self,i)用于增加一个数据点
- 定义用与计算数据点个数之和,平均值,最大值,最小值得实例对象方法,即count(),mean(),max(),min()
- 定义用于绘制简单直方图的实例对象方法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() #绘制简易直方图