学习内容:
接着25_3_18 python进阶学习.
1. python类和对象
1.1 继承/派生
继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力,其目的是延续旧的类的功能,使用继承派生机制,可以将一些共有功能加在基类中,实现代码的共享 .
派生就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为,其目地是在旧类的基础上添加新的功能,并在不改变基类的代码的基础上改变原有类的功能 .
1)单继承
单继承说明:单继承是指派生类由一个基类衍生出来的 .
单继承的语法:class 类名(基类名):
语句块
示例:
class Human: # 人类的共性
def say(self, what): # 说话
print("说:", what)
def walk(self, distance): # 走路
print("走了", distance, "公里")
class Student(Human):
def study(self, subject): # 学习
print("学习:", subject)
class Teacher(Human):
def teach(self, language):
print("教:", language)
h1 = Human()
h1.say("天气真好!")
h1.walk(5)
s1 = Student()
s1.walk(4)
s1.say("感觉有点累")
s1.study("python")
t1 = Teacher()
t1.teach("面向对象")
t1.walk(6)
t1.say("一会吃点什么好呢")
2)多继承
Python支持多继承形式,多继承的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法 .
示例:
# 类定义
class people:
# 定义基本属性
name = ''
age = 0
# 定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
# 定义构造方法
def __init__(self, n, a, w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" % (self.name, self.age))
# 另一个类,多继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self, n, t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s" % (self.name, self.topic))
# 多继承
class sample(people, speaker):
def __init__(self, n, a, w, t):
people.__init__(self, n, a, w)
speaker.__init__(self, n, t)
test = sample("Tim", 25, 80, "Python")
test.speak() # 方法名同,默认调用的是在括号中参数位置排前父类的方法
3)覆盖 override
覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类的实例调用该方法时,实际调用的是子类中的覆盖版本,这种现象叫覆盖,其作用是为了实现和父类同名,但功能不同的方法.
示例:
class A:
def work(self):
print("A.work 被调用!")
class B(A):
'''B类继承自A类'''
def work(self):
print("B.work 被调用!!!")
pass
b = B()
b.work() # 请问调用谁? B
a = A()
a.work() # 请问调用谁? A
1.2 封装 enclosure
封装(Encapsulation)是一种将数据(属性)和操作数据的方法(方法)绑定在一起的机制。封装的主要目的是隐藏对象的内部状态,并对外部提供有限的访问接口,通过封装,可以防止外部代码直接访问对象的内部数据,从而提高代码的安全性和可维护性 .
1)私有属性(Private Attributes)
在 Python 中,没有严格的私有属性,但可以通过命名约定来模拟私有属性,通常,使用单下划线 '_' 或双下划线 '__' 来表示私有属性 .
单下划线 '_' 前缀表示属性是“受保护的”,建议外部代码不要直接访问,并且单下划线前缀 '_' 是一种约定,而不是语言层面的强制规则,这种约定并没有强制性,外部代码仍然可以直接访问这些属性或方法 .
示例:
class MyClass:
def __init__(self):
self._protected_attr = 42
obj = MyClass()
print(obj._protected_attr)
obj._protected_attr = 43
print(obj._protected_attr)
双下划线 '__' 前缀表示属性是“私有的”,Python 会对其进行名称改写(name mangling),使其在类外部难以直接访问 .
示例:
class MyClass:
def __init__(self):
self.__private_attr = 43
obj = MyClass()
print(obj.__private_attr) # AttributeError: 'MyClass' object has no attribute '__private_attr'
上述代码因为私有变量被python修改了名称,名称改写的规则是将属性名前面加上类名和一个下划线 '_',假设类MyClass,其中有一个私有属性 __private_attr,那么python 会将这个属性名改写为 _MyClass__private_attr .
示例:
class MyClass:
def __init__(self):
self.__private_attr = 43
obj = MyClass()
print(obj._MyClass__private_attr)
2)属性访问器(Getter 和 Setter)
单下划线 '_' 和双下划线'__'的变量虽然可以直接或间接被访问,但python不建议这么做,而如果需要访问或修改这些属性,应该通过类提供的公共方法' getter ' 和 ' setter ' 来控制对属性的访问和修改 .
示例:
class MyClass:
def __init__(self):
self._value = 42
def get_value(self):
return self._value
def set_value(self, value):
if value > 0:
self._value = value
else:
raise ValueError("Value must be positive")
obj = MyClass()
print(obj.get_value()) # 输出: 42
obj.set_value(100)
print(obj.get_value()) # 输出: 100
3)使用 '@property' 装饰器
Python 提供了 '@property' 装饰器,可以更简洁地实现属性的访问和修改,'@property'是 python中的一个装饰器,用于将类的方法转换为属性,'@property' 装饰器用于定义一个只读属性,而通过使用 '@property' 可以将方法的调用方式转换为属性的访问方式,从而使代码更加简洁和易读 .
如果需要设置属性的值,可以使用 '@property.setter' 装饰器定义一个可写属性,而 '@property' 通常与 ' @<property_name>.setter ' 一起使用,以实现属性的读取和写入操作 .
示例:
class MyClass:
def __init__(self):
self._value = 42
@property
def value(self):
return self._value
@value.setter
def value(self, value):
if value > 0:
self._value = value
else:
raise ValueError("Value must be positive")
obj = MyClass()
print(obj.value) # 输出: 42
obj.value = 100
print(obj.value) # 输出: 100
1.3 多态 polymorphic
多态(Polymorphism)是指同一个方法在不同的类中有不同的实现,多态可以分为两种主要类型:静态多态(Static Polymorphism)和动态多态(Dynamic Polymorphism).
1.3.1 静态多态
静态多态也称为编译时多态(Compile-time Polymorphism),主要通过以下两种方式实现:
1)方法重载
方法重载是指在同一个类中定义多个同名但参数不同的方法。编译器根据调用时传递的参数类型和数量来决定调用哪个方法 .
示例:
class Calculator:
def add(self, a, b):
return a + b
def add(self, a, b, c):
return a + b + c
# 在 Python 中,方法重载并不直接支持,因为 Python 是动态类型语言
# 但可以通过默认参数或可变参数来模拟方法重载
class Calculator:
def add(self, a, b, c=0):
return a + b + c
calc = Calculator()
print(calc.add(1, 2)) # 输出: 3
print(calc.add(1, 2, 3)) # 输出: 6
2) 运算符重载
运算符重载(Operator Overloading) 是指通过定义类的魔术方法(Magic Methods),使类的对象支持 Python 的内置运算符(如 +
、-
、*
、/
等),通过运算符重载,我们可以让自定义类的对象像内置类型(如整数、字符串、列表等)一样使用运算符 .
方法名 | 运算符 | 说明 |
__add__(self, rhs) | + | 加法 |
__sub__(self, rhs) | - | 减法 |
__mul__(self, rhs) | * | 乘法 |
__truediv__(self, rhs) | / | 除法 |
__floordiv__(self, rhs) | // | 整除 |
__mod__(self, rhs) | % | 取模(求余) |
__pow__(self, rhs) | ** | 幂 |
示例:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __str__(self):
return f"({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2
print(p3) # 输出: (4, 6)
1.3.2 动态多态
动态多态也称为运行时多态(Runtime Polymorphism),主要通过方法重写(Method Overriding)实现 .
1)方法重写
方法重写是指子类重新定义父类中已有的方法,从而在运行时根据对象的实际类型来调用相应的方法 .
示例:
class Animal:
def speak(self):
return "Some generic sound"
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
print(dog.speak())
print(cat.speak())
#输出:
Some generic sound
Some generic sound
注意:子类重写的方法必须与父类方法具有相同的名称和参数列表 .
示例:
class Animal:
def speak(self):
return "Some generic sound"
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
dog = Dog()
cat = Cat()
print(dog.speak()) # 输出: Woof!
print(cat.speak()) # 输出: Meow!
优化:提供一个公共方法,用来执行不同子类的方法 .
class Animal:
def speak(self):
return "Some generic sound"
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
def animal_speak(animal):
print(animal.speak())
dog = Dog()
cat = Cat()
animal_speak(dog)
animal_speak(cat)
2)鸭子类型
Python 是一种动态类型语言,支持鸭子类型(Duck Typing),鸭子类型的核心思想是"如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子"。这意味着只要对象实现了所需的方法,就可以在代码中使用它,而不需要显式地继承某个基类 .
示例:
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
# 多态的体现
def animal_sound(animal):
print(animal.speak())
dog = Dog()
cat = Cat()
animal_sound(dog) # 输出: Woof!
animal_sound(cat) # 输出: Meow!
在这个例子中,'Dog' 和 'Cat' 类并没有继承同一个基类,但它们都实现了 'speak' 方法。因此,'animal_sound' 函数可以接受这两个类的对象,并调用它们的 'speak' 方法 .
鸭子类型的优点:1. 灵活性高,减少了对类型声明的依赖,使得代码更加灵活;2. 简化代码,避免了复杂的继承体系和接口定义;3. 易于扩展,可以很容易地添加新的类型,只要它们实现了所需的方法或属性.
鸭子类型的缺点:1. 类型安全问题:由于不进行显式的类型检查,可能会在运行时引发错误,例如,如果传入的对象没有实现所需的方法,会抛出 'AttributeError';2. 调试困难,在大型项目中,由于缺乏显式的类型约束,可能会导致调试和维护更加困难.
3)抽象基类
Python 提供了 'abc' 模块,用于定义抽象基类,抽象基类不能直接实例化,而是作为其他类的模板,子类必须实现抽象基类中定义的抽象方法,否则会引发错误.
使用 'abc' 模块的基本步骤:1. 导入模块,导入 'abc' 模块中的 'ABC' 和 'abstractmethod';2. 定义抽象基类,通过继承 'ABC' 类来定义抽象基类;3. 定义抽象方法,使用 '@abstractmethod' 装饰器标记抽象方法;4. 实现子类,子类必须实现抽象基类中定义的所有抽象方法,否则无法被实例化.
示例:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
# 多态的体现
def animal_sound(animal):
print(animal.speak())
dog = Dog()
cat = Cat()
animal_sound(dog) # 输出: Woof!
animal_sound(cat) # 输出: Meow!
1.4 内建函数重写(了解)
在自定义类内添加相应的方法,让自定义类创建的实例像内建对象一样进行内建函数操作,然而,这样做通常是不推荐的,因为这可能会导致代码的可读性和可维护性降低,并且可能会引发意外的行为.
示例:
class MyNumber:
"此类用于定义一个自定义的类,用于演示str函数重写"
def __init__(self, value):
"构造函数,初始化MyNumber对象"
self.data = value
def __str__(self):
"转换为普通字符串"
return "%s" % self.data
n1 = MyNumber("一只猫")
n2 = MyNumber("一只狗")
print("str(n2) ===>", str(n2))
class MyList:
def __init__(self, iterable=()):
self.data = [x for x in iterable]
def __repr__(self):
return "MyList(%s)" % self.data
def __len__(self):
print("__len__(self) 被调用!")
return len(self.data)
def __abs__(self):
print("__len__(self) 被调用!")
return MyList((abs(x) for x in self.data))
myl = MyList([1, -2, 3, -4])
print(len(myl))
print(abs(myl))
1.5 super函数
1.5.1 MRO查找顺序
MRO(Method Resolution Order,方法解析顺序)描述了 python 在多重继承的情况下,如何查找类的方法和属性,MRO 决定了当一个类继承自多个父类时,python 如何确定方法和属性的查找顺序.
MRO 的计算规则:python 使用 C3 线性化算法来计算 MRO.
1. 子类优先于父类:子类的方法或属性会优先于父类被查找;
2. 父类的顺序保持不变:在定义类时,父类的顺序会影响 MRO。在多重继承中,按照继承列表的顺序从左到右查找;
3. 单调性:如果一个类出现在另一个类的 MRO 中,那么它的父类也应该出现在这个类的 MRO 中
示例:
# MRO
class A:
def method(self):
print('A')
class B(A):
def method(self):
print('B')
class C(A):
def method(self):
print('C')
class D(B, C):
def method(self):
print('D')
# class E(C, B): # Mro顺序不一致 报错
class E(B, C):
def method(self):
print('E')
class F(D, E):
pass
# print(D().method())
print(F.__mro__)
1.5.2 super()
super() 函数是用于调用父类(超类)的一个方法,它用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题.
super()是一个用于调用父类方法的函数,它依赖于 Python 的 MRO 机制。因此,'super()' 本身并不能解决 MRO 冲突,而是依赖于已经确定的 MRO.
super()解决多继承问题的关键在于 MRO(方法解析顺序),即 Python 按照某种顺序(通常是从左到右)依次调用继承链中的方法.
super()总是会调用下一个类的 process(),而不会重复调用同一个类的方法,使用' super()' 时,python 会动态查找类继承链中的下一个类。通过 MRO,避免了手动调用时可能发生的重复调用.
示例1: 'super()' 的无参数形式
class A:
def add(self, x):
y = x+1
print(y)
class B(A):
def add(self, x):
print("子类方法")
super().add(x)
b = B()
b.add(2) # 3
示例2:'super()'的带参数形式
class Parent: # 定义父类
def myMethod(self):
print('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print('调用子类方法')
super(Child, self).myMethod()
c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super().init():'super().__init__()' 是python中用于调用父类(基类)构造函数的一种方式,它通常用于子类的构造函数中,以确保父类的构造函数被正确调用和初始化,这在继承(inheritance)中尤为重要,因为父类的初始化代码可能包含设置实例变量或执行其他重要的初始化任务.
示例:
class Parent:
def __init__(self):
print("Parent class constructor called")
self.parent_attribute = "I am a parent attribute"
class Child(Parent):
def __init__(self):
super().__init__()
print("Child class constructor called")
self.child_attribute = "I am a child attribute"
# 创建一个 Child 类的实例
child_instance = Child()
print(child_instance.parent_attribute)
# 输出
# Parent class constructor called
# Child class constructor called
super().__init__()的作用:1.
代码重用:避免在子类中重复父类的初始化代码;2. 正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行;3. 支持多重继承:在多重继承情况下,super()
可以确保所有基类的构造函数都被正确调用.
示例:
class Attention(nn.Module):
def __init__(self, dim, heads=8, dim_head=64, dropout=0.):
super().__init__()
inner_dim = dim_head * heads # 计算内部维度
project_out = not (heads == 1 and dim_head == dim) # 判断是否需要投影输出