第二阶段知识点总结解释版【day32-day35】

发布时间 2023-06-29 19:02:13作者: Chimengmeng

知识点总结

day32

1.面向过程和面向对象优缺点,使用场景

  • 面向过程和面向对象都是编程的两种不同的范式。

  • 面向过程的优点:

    • 1.执行速度比面向对象更快。
    • 2.简单易懂,且不需要大量的规则或语法。
    • 3.它适合在小型程序中使用。
  • 面向过程的缺点:

    • 1.没有高度的拓展性。
    • 2.系统难以维护和管理。
    • 3.难以进行大规模的开发和维护。
  • 面向对象的优点:

    • 1.数据和函数有机结合,可提高编程代码的复用性和拓展性。
    • 2.可以封装和保护数据,提高了安全性和可靠性。
    • 3.对于大型程序,具有更好的维护性和可读性。
  • 面向对象的缺点:

    • 1.写起来比较麻烦,而且很容易出错。
    • 2.处理管道、框架、流等方法时,敏捷性较差。
    • 3.相较于面向过程,面向对象的代码会比较庞大。
  • 使用场景:

    • 当需要进行简单的操作,在时间紧迫和系统保护性低下时,我们可以使用面向过程。
    • 但是,在大型项目、企业应用程序和多学术性质的环境方面,使用面向对象更为常见。

2.如何定义类,写出一个例子,定义类的过程发生了那些事,如何产生对象,产生的对象有何特点

  • 定义类是指在面向对象编程中创建一个类,以描述某个具体事物的一系列属性和方法。
    • 类可以看做是一种抽象数据类型,用来描述某一特定类型的对象。
  • 以下是一个Python的类定义示例:
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'm {self.age} years old.")

  • 以上定义了一个名为Person的类,包含了__init__函数和say_hello方法。
    • __init__函数是Python内置的一个特殊方法,用于初始化类中的属性。
    • 在这个例子中,类的__init__方法会接收两个参数nameage,并将它们分别赋值给类中的属性self.nameself.agesay_hello方法则会输出当前实例的姓名和年龄。
  • 当我们要使用这个类时,可以通过下面的代码创建一个Person对象:
person1 = Person("Tom", 20)
person1.say_hello()
  • 在上面的代码中,我们首先通过Person("Tom", 20)来创建了一个名为person1的实例对象,并将其赋值给变量person1
    • 在这个过程中,Python会自动调用类中的__init__方法,并将参数传递给它。
  • 产生的对象特点:
    • 每个对象都是类的一个实例,当我们调用一个类创建对象时,就会在内存中分配一块空间来存储该对象的属性和方法。
    • 每个对象都有自己独特的属性值,并且可以执行类中定义的各种方法。
    • 因此,通过这种方式可以创建多个具有相同行为但属性值不同的实例。
    • 在以上的示例中,person1对象就是Person类的一个实例,具有自己独特的nameage属性值。

3.如何定制对象自己的属性

  • 在Python中,对象的自定属性通常可以通过为对象添加新的属性来实现。

  • 具体而言,可以通过以下操作来定制对象自己的属性:

【1】直接为对象添加属性

  • 通过赋值语句直接为对象添加属性即可,例如:
person = {"name": "Tom", "age": 20}
person.gender = "male"  # 为person对象添加gender属性
  • 这样,就成功为person对象添加了一个名为gender的属性,并将其值设为"male"

【2】通过方法为对象添加属性

  • 也可以通过定义特定的方法为对象添加属性,例如:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def set_gender(self, gender):
        self.gender = gender
  • 在上面的示例中,我们为Person类新增了一个名为set_gender的方法,该方法可以接收一个gender参数,并将其赋值给对象的gender属性。

【3】通过调用该方法即可为对象新增属性,例如:

person = Person("Tom", 20)
person.set_gender("male")  # 为person对象添加gender属性并设置值为"male"
  • 这样,就成功为person对象添加了一个名为gender的属性,并将其值设为"male"

【4】总结:

  • 在Python中,定制对象自己的属性主要有两种方式:
    • 直接为对象添加属性
    • 通过特定的方法为对象添加属性。
  • 需要根据具体场景选择不同的方式进行操作。

4.属性的查找顺序是怎样的

  • 在Python中,属性的查找顺序是按照以下规则进行的:

    • 首先查找对象自身是否具有该属性,如果有,则直接返回该属性值;
    • 如果对象自身没有该属性,则查找该对象所属类是否具有该属性,如果有,则返回该属性值;
    • 如果该对象所属类也没有该属性,则按照方法解析顺序(MRO)依次在父类中查找该属性,直到找到为止;
    • 如果在所有相关的类中都没有找到该属性,则抛出AttributeError异常。
  • 注意,属性的查找顺序是由Python中的类继承顺序决定的,即MRO。MRO是通过C3算法动态计算得出的原则性顺序,其计算顺序是从下往上、从左往右的方式。

  • 下面是一个简单的代码示例,来说明Python中属性的查找顺序:

class A:
    x = 1

class B(A):
    pass

class C(A):
    x = 2

class D(B, C):
    pass

d = D()
print(d.x)
  • 在上述代码中,我们定义了4个类A、B、C和D,并分别做出如下说明:

    • 类A具有一个属性x,其值为1;
    • 类B继承自类A;
    • 类C继承自类A,并重载了其属性x,将其值改为2;
    • 类D同时继承自类B和类C。
  • 然后我们创建了一个D类的实例d,并调用其属性x。

    • 根据属性查找顺序,程序将依次在对象本身、所属类D、父类B、父类C和父类A中查找x属性,并最终返回值为2。
  • 因此,上述代码的输出结果为:2。

  • 总之,在Python中面向对象编程过程中,理解属性查找顺序是非常重要的。

day33

1.分别写出一个绑定方法,非绑定方法的例子

绑定方法示例:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def introduce(self):
        print("Hi, my name is", self.name, "and I am", self.age, "years old.")
        
person1 = Person("John", 25)
method = person1.introduce   # 绑定方法
method()   # 输出:Hi, my name is John and I am 25 years old.

非绑定方法示例:

class Calculator:
    @staticmethod
    def add(a, b):
        return a + b
    
    @classmethod
    def multiply(cls, a, b):
        print("Class:", cls)
        return a * b
        
result1 = Calculator.add(3, 5)   # 静态方法可以通过类名直接调用,不需要创建实例
print(result1)   # 输出:8

result2 = Calculator.multiply(4, 6)   # 类方法可以通过类名直接调用
print(result2)   # 输出:Class: <class '__main__.Calculator'> 24
  • 在非绑定方法中,无需创建实例即可通过类名调用该方法,并且类方法第一个参数是类本身(通常命名为 cls),而不是实例本身。静态方法则没有类和实例的参数。

2.如何隐藏属性,写一个例子,隐藏之后发生了什么?

  • 在Python中,可以通过在属性名前添加双下划线 "__" 来实现属性的隐藏。
    • 被隐藏的属性将不再被直接访问,而是被重命名为 "_类名__属性名",从而避免了属性被意外修改。
  • 以下是一个例子:
class Person:
    def __init__(self, name):
        self.__name = name
        
person1 = Person("John")
print(person1.__name)  # 此处会抛出 AttributeError 异常,因为属性名被隐藏起来了
  • 在上面的例子中
    • 我们试图访问 person1__name 属性
    • 但是由于该属性已经被隐藏起来了
    • 所以我们会收到一个 AttributeError 异常的提示。
  • 隐藏属性可以帮助我们更好地封装数据,从而保证数据的安全性和可靠性。
    • 一旦属性被隐藏,就不能直接在类外部修改它们,只能通过提供的公共方法来更新或查看属性的值。
    • 这有助于避免意外的修改或错误,提高了代码的可靠性。

3.为什么要隐藏属性

  • 在面向对象编程中

    • 隐藏属性的目的是为了封装数据,保障数据的安全性和可靠性。
  • 具体来说,隐藏属性可以提供以下优点:

    • 明确访问权限:

      • 通过隐藏属性,我们可以限制外部对类的属性的访问,只允许通过特定的方法进行读写操作。

      • 这样可以明确哪些属性是公共的,哪些是私有的,有效防止了不当调用。

    • 减少代码中的错误:

      • 通过使用隐藏属性,可以避免对象被错误地修改或访问。

      • 例如,某些属性只能通过特定方法才能修改或检索。这样可以在编译时或运行时发现一些错误,减轻了调试的难度。

    • 支持修改内部实现:

      • 将属性隐藏起来后,我们可以在不破坏类接口的前提下更改内部实现,从而简化程序,又不会影响到外部的接口。
      • 这也是面向对象编程的一个基本原则——“接口稳定原则”。
  • 综上所述,隐藏属性可以提高代码的安全性和可靠性,是面向对象编程中非常重要的一个特性。

4.property装饰器的用法

@property 装饰器是Python中一种常用的装饰器,用于定义类的属性,使得属性的读写行为更加合理和规范。下面是@property 装饰器的用法。

如下是一个简单的示例:

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

    @property
    def width(self):
        return self._width
    
    @width.setter
    def width(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError("Width must be numeric")
        if value < 0:
            raise ValueError("Width must be non-negative")
        self._width = value
        
    @property
    def height(self):
        return self._height
    
    @height.setter
    def height(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError("Height must be numeric")
        if value < 0:
            raise ValueError("Height must be non-negative")
        self._height = value
  • 在这个示例中,我们定义了一个矩形类(Rectangle)
    • 通过@property 装饰器定义了三个属性:area、width、height。
    • 其中,area 是只读属性,而 width 和 height 则是可读写属性。
  • @property 装饰器的作用是将一个方法转换为相应的只读属性
    • 使得类的属性读取更加方便和直观。
    • 例如,我们可以直接通过访问 rectangle.area 属性获取矩形面积
      • 而不是调用 get_area() 方法。
  • 同时,我们可以通过 @property 装饰器结合 setter 方法来定义可读写属性。
    • 例如,在上述示例中,我们定义了 width 和 height 两个可读写属性,通过对应的 setter 方法可以限制该属性的取值范围。
  • 总之,@property 装饰器能够让我们以属性的方式访问类的方法,并提供更加合理和规范的读写行为。

day34

1.什么是继承?为什么使用继承,写一个继承类的例子

  • 继承是面向对象程序设计中的一个重要概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。

    • 通过继承,子类可以直接访问和使用父类中已经定义好的属性和方法,从而避免了重复编写相同的代码。
  • 使用继承的主要目的是实现代码重用和派生类的特化。

    • 通过继承,我们可以在不修改现有代码的情况下,对已有类的行为进行修改或进行功能扩展。
    • 这种机制提高了代码的可维护性,减少了代码冗余,同时也符合面向对象编程的设计原则。
  • 以下是一个简单的继承类的例子:

class Animal:  # 父类/基类
    def __init__(self, name):
        self.name = name
    
    def sound(self):
        pass

class Dog(Animal):  # 子类/派生类
    def __init__(self, name):
        super().__init__(name)
    
    def sound(self):
        return "汪汪!"
    
class Cat(Animal):  # 子类/派生类
    def __init__(self, name):
        super().__init__(name)
    
    def sound(self):
        return "喵喵!"

# 创建对象并调用方法
dog = Dog("旺财")
print(dog.name)   # 输出:旺财
print(dog.sound())  # 输出:汪汪!

cat = Cat("咪咪")
print(cat.name)   # 输出:咪咪
print(cat.sound())  # 输出:喵喵!
  • 在上述例子中,Animal 是一个父类,它定义了动物的共同属性和方法。
    • Dog 和 Cat 是 Animal 类的子类,继承了它的属性和方法,并可以根据需要进行自定义实现。
    • 通过继承,我们可以避免在 Dog 和 Cat 类中重复定义 name 属性和 sound() 方法,提高了代码的可复用性和可维护性。

2.单继承和多继承下的属性查找顺序

  • 在单继承和多继承下,属性的查找顺序有所不同。

    • 单继承下的属性查找顺序(深度优先搜索):

      • 当一个类通过单继承与一个父类关联时,属性的查找顺序遵循深度优先搜索原则。

      • 具体查找步骤如下:

        • a. 首先搜索当前类是否有该属性,如果有,则直接返回对应的值。

        • b. 如果当前类没有该属性,则沿着继承链向上查找父类,并依次检查每个父类是否有该属性,直到找到第一个拥有该属性的父类,然后返回对应的值。

        • c. 如果所有的父类都没有该属性,则抛出属性错误(AttributeError)。

    • 多继承下的属性查找顺序(C3 算法):

      • 在多继承情况下,即一个子类同时继承自多个父类时,Python 使用 C3 算法来确定属性的查找顺序。
      • C3 算法通过广度优先搜索来决定继承链中的属性查找顺序,保证了所有父类的属性都能被正确访问,同时避免了潜在的冲突和歧义
  • 具体的 C3 算法规则过于复杂,无法通过简单的文字描述。

    • 它涉及到了多个因素如类的线性化顺序、继承深度等。
    • 在 Python 中,可以使用 mro() 方法来查看类的方法解析顺序(Method Resolution Order)。
  • 需要注意的是,对于属性查找顺序而言,Python 3 采用了新式类 (new-style class) 的解析顺序,而不再考虑经典类 (classic class)。

    • 新式类是指显式继承自 object 或其子类的类
    • 而经典类则是指没有显式继承自 object 的类。
  • 在 Python 2 中,如果没有显式继承自 object,会按照深度优先搜索的方式进行属性查找。因此,在多继承时,使用新式类可以更好地控制属性查找顺序。

总结起来,单继承下的属性查找顺序是深度优先搜索,而多继承下的属性查找顺序通过 C3 算法决定。

3.super和mro如何使用,举例说明

  • 在Python中,super()函数和方法解析顺序(MRO)是用于处理多继承情况下的父类调用和属性查找问题的工具。

  • super()函数用于在子类中调用父类的方法。

    • 它的一般用法是super().method_name(),其中method_name是要调用的父类方法的名称。
    • 这样,子类可以通过super()函数来调用父类的方法,并且不需要显式指定父类的名称。
  • 举个例子,假设有一个Shape基类和两个派生类RectangleCircle,如下所示:

class Shape:
    def __init__(self, color):
        self.color = color

class Rectangle(Shape):
    def __init__(self, color, width, height):
        super().__init__(color)
        self.width = width
        self.height = height

class Circle(Shape):
    def __init__(self, color, radius):
        super().__init__(color)
        self.radius = radius

  • 在上面的示例中,RectangleCircle类都是从Shape类继承而来。
  • 在子类的构造函数中,我们使用super().__init__(color)来调用父类Shape的构造函数,并传递color参数。
  • 这样,子类就可以继承并初始化父类的属性,同时可以添加自己的属性。
  • 另外一个与多继承相关的概念是方法解析顺序(MRO)。
    • MRO决定了在多继承情况下,方法和属性的解析顺序。
    • 你可以通过调用类的mro()方法来查看方法解析顺序。
  • 举个例子,假设有三个类ABC,其中C继承自BB 继承自A。通过调用mro()方法查看方法解析顺序:
class A:
    def hello(self):
        print("Hello from A")

class B(A):
    def hello(self):
        print("Hello from B")

class C(B):
    def hello(self):
        print("Hello from C")

print(C.mro())
  • 执行上述代码,输出结果为:[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
  • 这意味着,在类C的实例中调用hello()方法时,方法解析顺序为 C -> B -> A
  • 使用super()函数和MRO能够很好地处理多继承情况下的父类调用和属性查找问题,使得代码更加清晰和易读。

4.写一个组合的例子

  • 当使用组合关系时,一个对象包含了其他对象作为其属性,而不是继承它们。
    • 这种关系可以通过将一个类的实例作为另一个类的属性来建立。
  • 下面是一个使用组合关系的示例,假设我们有两个类:
    • Engine(引擎)和Car(汽车)。
    • Car类使用组合关系包含一个Engine对象作为其属性。
class Engine:
    def __init__(self, horsepower):
        self.horsepower = horsepower

    def start(self):
        print("Engine started")

    def stop(self):
        print("Engine stopped")

class Car:
    def __init__(self, make, model, horsepower):
        self.make = make
        self.model = model
        self.engine = Engine(horsepower)

    def start(self):
        print(f"{self.make} {self.model} started")
        self.engine.start()

    def stop(self):
        print(f"{self.make} {self.model} stopped")
        self.engine.stop()
  • 在上面的示例中,Engine类表示汽车的引擎,具有horsepower属性和start()stop()方法。

    • Car类表示汽车,它使用组合关系将一个Engine对象作为其属性。
  • Car类的构造函数中,通过创建一个Engine对象并将其赋值给engine属性,以实现将引擎与汽车关联起来。

  • Car类还定义了start()stop()方法,这些方法会在启动和停止汽时调用引擎的start()stop()方法。

  • 下面是一个示例的使用:

engine = Engine(200)
car = Car("Toyota", "Camry", 200)

car.start()  # 输出:"Toyota Camry started","Engine started"
car.stop()   # 输出:"Toyota Camry stopped","Engine stopped"
  • 通过使用组合关系,Car类能够包含一个引擎对象,并在启动和停止汽车时调用引擎的方法。这样我们可以更灵活地构建复杂的对象结构,并将对象之间的关联关系更清晰地表达出来。

day35

1.列出你所知道的魔术方法,分别在什么时候执行

  • 魔术方法是一种在特定情况下自动执行的特殊方法。
    • 以下是一些常见的魔术方法及其执行时机:
  1. __init__: 当创建一个对象实例时调用,用于初始化对象的属性和状态。
  2. __del__: 当一个对象被销毁时调用,用于释放对象占用的资源。
  3. __str__: 当使用内置的str()函数或打印对象时调用,返回对象的字符串表示形式。
  4. __repr__: 当使用内置的repr()函数或在交互式环境中显示对象时调用,返回对象的可打印字符串表示形式,通常用于调试和诊断。
  5. __len__: 当使用内置的len()函数获取对象的长度时调用。
  6. __getitem__: 当使用索引访问对象的元素时调用,例如obj[key]
  7. __setitem__: 当给对象的元素赋值时调用,例如obj[key] = value
  8. __getattr__: 当尝试访问对象的不存在的属性时调用,提供动态访问属性的机制。
  9. __setattr__: 当设置对象的属性时调用,用于拦截和控制属性赋值操作。
  10. __call__: 当将对象作为函数调用时调用,使得对象可以像函数一样被调用。
  • 请注意,这只是一些常见的魔术方法示例,Python还提供了其他许多魔术方法,用于自定义类的行为和操作。
    • 每个魔术方法的功能和执行时机可以根据具体情况进行定制。

2.反射有什么用,如何用

反射是一种在运行时检查、访问和修改对象的能力。它允许程序在不事先知道对象结构的情况下,动态地观察、调用和操作对象的属性、方法和类。

反射有以下几个主要用途:

  1. 动态获取对象的信息:通过反射,可以在运行时获取对象的类名、属性名、方法名等相关信息。这对于编写通用的代码、调试和诊断以及框架开发非常有用。

  2. 动态调用对象的方法:通过反射,可以动态调用对象的方法,即使在编写代码时不知道方法的名称。这在需要根据条件来选择不同方法时非常有用。

  3. 动态访问和修改对象属性:通过反射,可以在运行时访问和修改对象的属性值。这对于需要动态配置对象或进行对象状态管理的场景很有帮助。

  4. 创建对象和执行类的实例化:通过反射,可以在运行时实例化一个类,并传入相应的参数。这对于需要动态创建对象的场景非常有用。

下面是一些使用反射的示例:

  1. 获取对象的类名:
class MyClass:
    pass

obj = MyClass()
class_name = obj.__class__.__name__  # 使用反射获取类名
print(class_name)  # 输出:MyClass
  1. 动态调用对象方法:
class MyClass:
    def my_method(self):
        print("Hello, World!")

obj = MyClass()
method_name = 'my_method'
method = getattr(obj, method_name)  # 使用反射获取方法对象
method()  # 输出:Hello, World!
  1. 动态访问和修改对象的属性:
class MyClass:
    def __init__(self):
        self.my_attribute = 42

obj = MyClass()
attribute_name = 'my_attribute'
attribute_value = getattr(obj, attribute_name)  # 使用反射获取属性值
print(attribute_value)  # 输出:42

setattr(obj, attribute_name, 100)  # 使用反射设置属性值
print(obj.my_attribute)  # 输出:100
  1. 创建对象和执行类的实例化:
class MyClass:
    def __init__(self, value):
        self.my_attribute = value

class_name = 'MyClass'
args = (42,)  # 构造参数元组
obj = globals()[class_name](*args)  # 使用反射创建对象
print(obj.my_attribute)  # 输出:42

3.不使用抽象类,如何做到父类限制子类的行为,写出具体代码逻辑

在Python中,如果不使用抽象类,可以通过在父类中使用特殊的命名约定和异常来限制子类的行为。以下是一个示例代码逻辑:

class ParentClass:
    def restricted_method(self):
        # 在父类中定义一个被限制的方法,使用特殊的命名约定
        raise NotImplementedError("子类必须实现restricted_method方法")

class ChildClass(ParentClass):
    def restricted_method(self):
        # 子类中实现restricted_method方法
        super().restricted_method()  # 可以选择调用父类的方法逻辑
        # 其他子类特定的逻辑

child = ChildClass()
child.restricted_method()
  • 在上述代码中,父类ParentClass中定义了一个被限制的方法restricted_method,并抛出NotImplementedError异常提示子类必须实现该方法。
    • 子类ChildClass继承自父类,并重写了restricted_method方法来实现自己的逻辑。
    • 在子类实例化并调用restricted_method时,会执行子类中的实现。
  • 通过这种方式,父类限制了子类必须实现某个方法,并防止子类忘记实现或者不符合要求地实现该方法。
    • 当子类没有正确实现被限制方法时,会抛出NotImplementedError异常,提醒开发者进行相应的修改与实现。
  • 尽管这种方式并不像抽象类一样严格,但通过命名约定和异常的使用,可以达到对子类行为的一定程度限制,并促使开发者遵守相应的设计要求。