知识点总结
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__
方法会接收两个参数name
和age
,并将它们分别赋值给类中的属性self.name
和self.age
。say_hello
方法则会输出当前实例的姓名和年龄。
- 当我们要使用这个类时,可以通过下面的代码创建一个
Person
对象:
person1 = Person("Tom", 20)
person1.say_hello()
- 在上面的代码中,我们首先通过
Person("Tom", 20)
来创建了一个名为person1
的实例对象,并将其赋值给变量person1
。- 在这个过程中,Python会自动调用类中的
__init__
方法,并将参数传递给它。
- 在这个过程中,Python会自动调用类中的
- 产生的对象特点:
- 每个对象都是类的一个实例,当我们调用一个类创建对象时,就会在内存中分配一块空间来存储该对象的属性和方法。
- 每个对象都有自己独特的属性值,并且可以执行类中定义的各种方法。
- 因此,通过这种方式可以创建多个具有相同行为但属性值不同的实例。
- 在以上的示例中,
person1
对象就是Person
类的一个实例,具有自己独特的name
和age
属性值。
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
基类和两个派生类Rectangle
和Circle
,如下所示:
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
- 在上面的示例中,
Rectangle
和Circle
类都是从Shape
类继承而来。 - 在子类的构造函数中,我们使用
super().__init__(color)
来调用父类Shape
的构造函数,并传递color
参数。 - 这样,子类就可以继承并初始化父类的属性,同时可以添加自己的属性。
- 另外一个与多继承相关的概念是方法解析顺序(MRO)。
- MRO决定了在多继承情况下,方法和属性的解析顺序。
- 你可以通过调用类的
mro()
方法来查看方法解析顺序。
- 举个例子,假设有三个类
A
、B
和C
,其中C
继承自B
,B
继承自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.列出你所知道的魔术方法,分别在什么时候执行
- 魔术方法是一种在特定情况下自动执行的特殊方法。
- 以下是一些常见的魔术方法及其执行时机:
__init__
: 当创建一个对象实例时调用,用于初始化对象的属性和状态。__del__
: 当一个对象被销毁时调用,用于释放对象占用的资源。__str__
: 当使用内置的str()
函数或打印对象时调用,返回对象的字符串表示形式。__repr__
: 当使用内置的repr()
函数或在交互式环境中显示对象时调用,返回对象的可打印字符串表示形式,通常用于调试和诊断。__len__
: 当使用内置的len()
函数获取对象的长度时调用。__getitem__
: 当使用索引访问对象的元素时调用,例如obj[key]
。__setitem__
: 当给对象的元素赋值时调用,例如obj[key] = value
。__getattr__
: 当尝试访问对象的不存在的属性时调用,提供动态访问属性的机制。__setattr__
: 当设置对象的属性时调用,用于拦截和控制属性赋值操作。__call__
: 当将对象作为函数调用时调用,使得对象可以像函数一样被调用。
- 请注意,这只是一些常见的魔术方法示例,Python还提供了其他许多魔术方法,用于自定义类的行为和操作。
- 每个魔术方法的功能和执行时机可以根据具体情况进行定制。
2.反射有什么用,如何用
反射是一种在运行时检查、访问和修改对象的能力。它允许程序在不事先知道对象结构的情况下,动态地观察、调用和操作对象的属性、方法和类。
反射有以下几个主要用途:
-
动态获取对象的信息:通过反射,可以在运行时获取对象的类名、属性名、方法名等相关信息。这对于编写通用的代码、调试和诊断以及框架开发非常有用。
-
动态调用对象的方法:通过反射,可以动态调用对象的方法,即使在编写代码时不知道方法的名称。这在需要根据条件来选择不同方法时非常有用。
-
动态访问和修改对象属性:通过反射,可以在运行时访问和修改对象的属性值。这对于需要动态配置对象或进行对象状态管理的场景很有帮助。
-
创建对象和执行类的实例化:通过反射,可以在运行时实例化一个类,并传入相应的参数。这对于需要动态创建对象的场景非常有用。
下面是一些使用反射的示例:
- 获取对象的类名:
class MyClass:
pass
obj = MyClass()
class_name = obj.__class__.__name__ # 使用反射获取类名
print(class_name) # 输出:MyClass
- 动态调用对象方法:
class MyClass:
def my_method(self):
print("Hello, World!")
obj = MyClass()
method_name = 'my_method'
method = getattr(obj, method_name) # 使用反射获取方法对象
method() # 输出:Hello, World!
- 动态访问和修改对象的属性:
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
- 创建对象和执行类的实例化:
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
异常,提醒开发者进行相应的修改与实现。
- 当子类没有正确实现被限制方法时,会抛出
- 尽管这种方式并不像抽象类一样严格,但通过命名约定和异常的使用,可以达到对子类行为的一定程度限制,并促使开发者遵守相应的设计要求。