Python-面向对象(详细版)

面向对象

请添加图片描述

编程范式:是一种编程方法论或思维方式,它定义了解决问题和组织代码的一套规则和原则。编程范式描述了代码的结构、组织方式和编程风格,以及解决问题的方法和技术。

编程范式:面向过程编程、函数式编程、面向对象编程、并发编程、元编程

面向过程和面向对象编程的区别:

面向过程:将程序划分为一系列的过程或函数,这些函数按照一定的顺序执行。注重过程和步骤的顺序,通常使用全局变量来存储和操作程序的状态和数据。从上到下依次执行,着重于做什么。

面向对象 :将程序组织为一组相互关联的对象,每个对象都有自己的状态(属性)和行为(方法)。面向对象编程提供了封装、继承和多态等特性,能够更好地模拟现实世界的问题和关系。着重于谁(对象)去做

介绍

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量:定义在方法中的变量,只作用于当前实例的类。
  • 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

面向对象的三大特点:封装、继承、多态
变量对应属性、函数对应方法
封装:将属性和方法封存成类,不需要关系具体实现、只要关心怎么用就行了

# 以空调为例
# 属性:描述对象的具体信息(电器)
# 方法:具体功能(制冷、制热、除湿)

#空调是一个类
#海尔空调是一个子类
#海尔空调的一个具体的空调就是一个对象了

例子

类(Class)是一种创建对象的蓝图或模板。类定义了对象的属性(变量)和方法(函数),可以用于创建具有相同属性和行为的多个对象。

类继承机制支持多个基类,派生类可以覆盖基类的任何方法,类的方法可以调用基类中相同名称的方法。对象可以包含任意数量和类型的数据。和模块一样,类也拥有 Python 天然的动态特性:在运行时创建,创建后也可以修改。

定义一个ATM(银行)类,定义了常用语录、国家属性、银行初始金额

class ATM():
    # 属性 -- 变量
    info = "银行ATM存取款机"
    country = "中国"
    total_balance = 50000
    #方法
    def store_money(self,balance):
        self.total_balance += balance
        print(f"存款成功! toal_balance is {self.total_balance}")

如何使用ATM类呢,先实例化,创建一个类对象,然后调用对象的属性

# 实例化
a1 = ATM()
# 属性
a1.country
print(a1.country)
a1.total_balance
# 方法,让对象去做事情
a1.store_money(500)
中国
存款成功! toal_balance is 50500

介绍

当进入类定义时,将创建一个新的命名空间,并将其用作局部作用域 — 因此,所有对局部变量的赋值都是在这个新命名空间之内。 特别的,函数定义会绑定到这里的新函数名称。

定义一个ATM类:

class ATM():
    # 类属性 -- 变量
    info = "银行ATM存取款机"
    country = "中国"
    total_balance = 50000
    #方法
    def store_money(self,balance):
        # self为实例对象设定了自己的total_balance属性
        self.total_balance += balance   #这是最开始的self.total_balance(当前实例的)=self.total_balance(类的) +balance
        print(f"存款成功! toal_balance is {self.total_balance}")

类对象支持两种操作:属性引用、实例化

属性

任何跟在一个点号之后的名称都称为 属性 — 例如,在表达式 y.real 中,real 是对象 y 的一个属性。按严格的说法,对模块中名称的引用属于属性引用:在表达式 module1.func1 中,module1 是一个模块对象而 func1 是它的一个属性。在此情况下在模块的属性和模块中定义的全局名称之间正好存在一个直观的映射:它们共享相同的命名空间。

属性可以是只读或者可写的。如果为后者,那么对属性的赋值是可行的。模块属性是可写的,你可以写 module1.the_answer = 88 。可写的属性同样可以用del语句删除。例如,del module1.the_answer将会从名为module1的对象中移除the_answer` 属性.

# module1.py
the_answer = 66
import module1

# 访问属性
print(module1.the_answer)  # 输出: 66

# 修改属性
module1.the_answer = 88
print(module1.the_answer)  # 输出: 88

# 删除属性
del module1.the_answer
print(module1.the_answer)  # 抛出 AttributeError: module 'module1' has no attribute 'the_answer'

我们修改了属性的值为88。最后,使用del语句删除了属性the_answer。在删除属性后再次访问它将抛出AttributeError异常,因为该属性已被删除。

类对象

类对象支持两种操作:属性引用和实例化。

属性引用所使用的标准语法: obj.name

定义如下类:

class Oner:
    """ this is a example class"""
    h = 12345

    def f(self):
        return 'hello worl

那么 Oner.iOner.f 就是有效的属性引用,将分别返回一个整数和一个函数对象。 类属性也可以被赋值,因此可以通过赋值来更改 Oner.i 的值。 __doc__ 也是一个有效的属性,将返回所属类的文档字符串: "A simple example class"

类的 实例化 使用函数表示法。 可以把类对象视为是返回该类的一个新实例的不带参数的函数。如下:

x =Oner()

创建类的新 实例 并将此对象分配给局部变量 x

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter

x.counter是有关数据属性的操作

x.f 是有效的方法引用,因为 Oner.f 是一个函数,而 x.i 不是方法,因为 Oner.i 不是函数。 但是 x.fOner.f 并不是一回事 — 它是一个 方法对象,不是函数对象。

实例对象

实例对象所能理解的唯一操作是属性引用。
有两种有效的属性名称:数据属性和方法。
数据属性不需要声明;像局部变量一样,它们将在第一次被赋值时产生。

class Oner:
    """ this is a example class"""
    h = 12345

    def f(self):
        return 'hello world'

m = Oner()
m.count=1
print(m.count)
---------------------------------------------------------
1

方法是“从属于”对象的函数

m = Oner()
m.count=1
print(m.f())
print(m.count)

实例对象的有效方法名称依赖于其所属的类。 根据定义,一个类中所有是函数对象的属性都是定义了其实例的相应方法。 因此在我们的示例中,m.f 是有效的方法引用,因为 Oner.f 是一个函数,而 m.h 不是方法,因为 Oner.h 不是函数。 但是 m.fOner.f 并不是一回事 — 它是一个 方法对象,不是函数对象。

方法对象

一般,方法在绑定后立即被调用:

x.f()

Oner 示例中,这将返回字符串 'hello world'。 但是,立即调用一个方法并不是必须的: x.f 是一个方法对象,它可以被保存起来以后再调用。 例如:

x = Oner()
xf = x.f()
i = 0
for i in range(10):
    print(xf)

方法的特殊之处就在于实例对象会作为函数的第一个参数被传入。 在我们的示例中,调用 x.f() 其实就相当于 Oner.f(x)。 总之,调用一个具有 n 个参数的方法就相当于调用再多一个参数的对应函数,这个参数值为方法所属实例对象,位置在其他参数之前。

类空间以及实例空间

object.__class__ 

查看类或类对象的属性和方法
定义在类的属性以及方法作为共有属性,为了特出对象的特性,需要有对象独有的属性,这就是实例属性,实例属性的信息会被保存在对象的实例空间中。

# print(f"ATM id : {id(ATM)}")

实例化
每个对象都有一个类对象指针,可以访问类空间

#实例化
a1 = ATM()
a2 = ATM()

#属性或方法的使用,对象名.属性名    对象名.方法名()
# print(f"在使用store_money之前的a1.__dict__ is {a1.__dict__}")


print(f"before a1 total_balance is {a1.total_balance}")
# 1.使用对象查看属性的时候,先去当前空间查看
# 2.当前空间找不到的话,再通过类对象指针去类空间查找
# 3.如果类空间没有,就去父类空间找,依次类推,直到找最高层,或者找到属性为止
# 4.可以通过类对象指针访问类空间,但是无法通过对象名修改类空间属性的
print(f"before a1.__dict__ is {a1.__dict__}")

###################################

a1.store_money(6666)  #这是最开始的self.total_balance(当前实例的)=self.total_balance(类的) +balance
a1.store_money(6666)  #第二次调用的时候,两个self.total_balance都是实例空间里面的了,self.total_balance(当前实例的)=self.total_balance(这也是实例的) +balance

before a1 total_balance is 50000
#类实例化后,没有调用的时候,对象的空间为空,因此去访问父类的空间
before a1.__dict__ is {}

存款成功! toal_balance is 56666
存款成功! toal_balance is 63332
now a1.__dict__ is {'total_balance': 63332}

实例化多个对象,多个对象是有各自的内存地址,还是共享内存地址呢?

print(f"a1 info is {a1.info},a1 id is {id(a1)}")
print(f"a2.info is {a2.info},a2 id is {id(a2)}")

结果是:实例化对象的时候,都有各自的内存空间,互不影响

a1 info is 银行ATM存取款机,a1 id is 2886149246928
a2.info is 银行ATM存取款机,a2 id is 2886149246832

设置属性——实例属性:在我们实例化之后,我们可以设置对象的特有属性,就是实例属性了.

#设置属性  -- 实例属性
a2.bank = "建设银行"
a2.area = "湖南"
# 修改
a2.country = "China"


print(f"a1.area is {a1.country}")
print(f"a2.bank is {a2.bank},a2.area is {a2.area},a2.country is {a2.country}")
# 查看类空间
print(f"a1.__dict__ is {a1.__dict__}")
print(f"a2.__dict__ is {a2.__dict__}")

结果,如果同样的属性名称同时出现在实例和类中,则属性查找会优先选择实例

a1.area is 中国
a2.bank is 建设银行,a2.area is 湖南,a2.country is China
a1.__dict__ is {'total_balance': 63332}
#可以通过类对象指针访问类空间,但是无法通过对象名修改类空间属性的
a2.__dict__ is {'bank': '建设银行', 'area': '湖南', 'country': 'China'}

共享数据可能在涉及 mutable 对象例如列表和字典的时候导致令人惊讶的结果。 例如以下代码中的 skill 列表不应该被用作类变量,因为所有的 Dog 实例将只共享一个单独的列表.

class Dog:

    skill = []
    kind = '中华田园犬'

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

    def add_skill(self,skill):
        self.skill.append(skill)

dog = Dog("大黄")
dog1 = Dog("大黑")

dog.add_skill("死亡翻滚")
dog.add_skill("沉痛打击")

print(dog.skill)
----------------------------------
['死亡翻滚', '沉痛打击']

如果向让skill列表分开使用,应该改为

   def add_skill(self, trick):
        self.skill.append(trick)

类空间和实例空间图解

请添加图片描述

继承

当在Python中定义一个类时,可以使用继承来从现有类派生出新的类。继承允许子类(派生类)继承父类(基类)的属性和方法,并且可以在子类中添加新的属性和方法,或者修改继承的属性和方法。

定义类

class A:
    pass
class A():
    pass
class A(object):
    pass

这上面三种都可以定义类,
原因:在Python3中,默认就会继承object

下面是关于Python类继承的一些详细说明:

  1. 定义基类(父类):
    定义一个基类时,需要使用class关键字,后跟类的名称。基类可以包含属性和方法的定义。例如:

    class Animal:
        def __init__(self, name):
            self.name = name
    
        def speak(self):
            print("The animal speaks.")
    

    在上述示例中,Animal类具有一个构造函数__init__和一个speak方法。

  2. 定义派生类(子类):
    要从基类派生出一个子类,只需在类定义时在类名后面使用圆括号指定基类的名称。例如:

    class Dog(Animal):
        def speak(self):
            print("The dog barks.")
    

    在上述示例中,Dog类从Animal类继承,并重写了speak方法。

  3. 创建对象:
    现在可以创建基类或派生类的对象。例如:

    animal = Animal("Animal")
    dog = Dog("Dog")
    
  4. 访问继承的属性和方法:
    对象可以访问其继承的属性和方法。例如:

    print(animal.name)  # 输出: Animal
    animal.speak()  # 输出: The animal speaks.
    
    print(dog.name)  # 输出: Dog
    dog.speak()  # 输出: The dog barks.
    

    派生类对象可以访问基类中定义的属性和方法,因为它们被继承了。

  5. 调用基类的方法:
    在派生类中,可以通过使用super()函数来调用基类的方法。例如:

    class Cat(Animal):
        def speak(self):
            super().speak()
            print("The cat meows.")
    

    在上述示例中,Cat类继承了Animal类,并通过super().speak()调用了基类的speak方法。

    当一个派生类继承了一个基类的方法,并且在派生类中重新定义了该方法,那么当该派生类的对象调用这个方法时,会执行派生类中的方法,而不是基类中的方法。这是因为方法调用在运行时会动态地绑定到对象的类型。

  6. 多重继承

    定义一个动物类和子类狗

    class A:
        name = "A"
        def __init__(self,count):
            print("this is A __init__")
            self.count = count
            self.desc = "class_A"
        def call(self):
            print("this is class A")
    
    class B(A):
        # def __init__(self):
        #     print("this is B __init__")
        def say(self):
            print("this is class B")
    
    # 可以多重继承
    class C(B):
        def __init__(self,C_name,count):
            super().__init__(count)              #在子类里面访问父类方法的时候使用
            # print("this is A __init__")
            # self.count = count
            # self.desc = "class_A"
            print("this is C __init__")
            self.C_name = C_name
            self.count = count +100
    
            #使用super时候最好放在自己定义的代码的前面
            # super().__init__(count)              #在子类里面访问父类方法的时候使用
            # print("this is A __init__")
            # self.count = count
            # self.desc = "class_A"
    

    在这个例子中,子类Dog继承了父类Animal的所有属性和方法。子类可以重写父类的方法,也可以添加新的方法。

  7. 继承多个父类

当一个类需要同时继承多个父类的功能时,可以使用多重继承。

class Animal:
    def __init__(self, name):
        self.name = name
    
    def eat(self):
        print(f"{self.name} is eating.")

class Flyable:
    def fly(self):
        print(f"{self.name} is flying.")

class Swimmable:
    def swim(self):
        print(f"{self.name} is swimming.")

class Bird(Animal, Flyable):
    def __init__(self, name):
        super().__init__(name)

class Fish(Animal, Swimmable):
    def __init__(self, name):
        super().__init__(name)

# 创建Bird对象

bird = Bird("Sparrow")
bird.eat()   # 调用继承自Animal的方法
bird.fly()   # 调用继承自Flyable的方法

# 创建Fish对象

fish = Fish("Salmon")
fish.eat()   # 调用继承自Animal的方法
fish.swim()  # 调用继承自Swimmable的方法

在这个例子中,Bird类继承了Animal类和Flyable类,而Fish类继承了Animal类和Swimmable类。通过多重继承,Bird类可以使用Animal类的eat()方法和Flyable类的fly()方法,而Fish类可以使用Animal类的eat()方法和Swimmable类的swim()方法。

继承允许代码重用和层次结构的构建,通过将通用功能放在基类中,而将特定的功能放在派生类中,提高了代码的可维护性和可扩展性。

super函数

在Python 3中,super() 是一个内置函数,用于调用父类(基类)的方法。它提供了一种方便的方式来访问和调用继承自基类的方法。

super() 函数主要用于两个方面:

  1. 在派生类中调用基类的方法:通过 super().method() 的形式,可以在派生类中调用基类的方法。这样可以继续执行基类的逻辑,并添加派生类特定的行为。

  2. 在多重继承中解决方法解析顺序(Method Resolution Order,MRO)问题:当一个类继承自多个父类时,super() 可以按照特定的方法解析顺序,依次调用父类的方法。

以下是使用 super() 的示例:

class BaseClass:
    def __init__(self):
        print("BaseClass init")

     def some_method(self):
         print("BaseClass method")

class DerivedClass(BaseClass):
    def __init__(self):
        super().__init__()  # 调用父类的 __init__ 方法
        print("DerivedClass init")

     def some_method(self):
         super().some_method()  # 调用父类的 some_method 方法
         print("DerivedClass method")

obj = DerivedClass()
obj.some_method()

输出:

BaseClass init
DerivedClass init
BaseClass method
DerivedClass method

在上述示例中,DerivedClass 是一个派生类,继承自 BaseClass。在派生类的 __init__ 方法中,通过 super().__init__() 调用了基类的 __init__ 方法,以确保基类的初始化逻辑得到执行。类似地,在派生类的 some_method 方法中,通过 super().some_method() 调用了基类的 some_method 方法,以便在派生类特定的行为之前或之后执行基类的方法。

使用 super() 的好处是,它可以自动解决方法解析顺序(MRO)问题。这在多重继承的情况下特别有用,因为 super() 会按照正确的顺序调用各个父类的方法,避免了手动调整顺序带来的麻烦。

私有成员

class Parent:
    tmp = "tmp"
    _min = 0
    __max = 10 #私有成员

    def __init__(self):
        self.name = "sc"
        self._sex = "male"
        self.__age = "22"

    def __make(self):
        print("这是一个私有方法")

    def show(self):
        print(f"age is {self.__age}")
        print(f"max is {self.__max}")
        # 变为了self._Parent__max和__age了


class Child(Parent):
    def show(self):
        print(f"max is {self.__max}")
        #这里寻找_Child__max


c1 = Child()
print(c1.__dict__)

那种仅限从一个对象内部访问的“私有”实例变量在 Python 中并不存在。 但是,大多数 Python 代码都遵循这样一个约定:带有一个下划线的名称 (例如 _age) 应该被当作是 API 的非公有部分 (无论它是函数、方法或是数据成员)。 这应当被视为一个实现细节,可能不经通知即加以改变。

由于存在对于类私有成员的有效使用场景(例如避免名称与子类所定义的名称相冲突),因此存在对此种机制的有限支持,称为 名称改写。 任何形式为 __age 的标识符(至少带有两个前缀下划线,至多一个后缀下划线)的文本将被替换为_Parent__age,其中Parent` 为去除了前缀下划线的当前类名称。

多态

多态是面向对象编程中的一个重要概念,它允许不同的对象对同一个方法产生不同的行为。在Python中,多态性是通过方法的动态绑定和运行时的类型识别来实现的。

简单来说,多态性使得我们可以使用统一的接口来处理不同类型的对象,而无需关心对象的具体类型。这样可以增加代码的灵活性和可扩展性。

下面是一个示例来说明多态性的概念:

class Animal:
    def sound(self):
        pass

class Cat(Animal):
    def sound(self):
        print("Meow!")

class Dog(Animal):
    def sound(self):
        print("Woof!")

def make_sound(animal):
    animal.sound()

# 创建对象

cat = Cat()
dog = Dog()

# 调用make_sound函数,传入不同类型的对象

make_sound(cat)  # 输出:Meow!
make_sound(dog)  # 输出:Woof!

在上面的例子中,Animal类是一个基类,Cat类和Dog类是它的子类。每个子类都覆盖了基类的sound()方法,分别输出猫的声音和狗的声音。

make_sound()函数接受一个Animal类型的参数,并调用传入对象的sound()方法。在调用make_sound()函数时,我们可以传入不同类型的对象(如Cat对象或Dog对象),但无论传入什么类型的对象,make_sound()函数都能正确地调用对象的sound()方法,产生相应的声音输出。这就是多态性的体现,通过统一的接口,我们可以处理不同类型的对象,而无需关心对象的具体类型。

总结起来,多态性在面向对象编程中允许我们使用统一的接口来处理不同类型的对象,使得代码更加灵活、可扩展和易于维护。

__init__函数的使用

 __init__方法:
 构造方法,实例初始化方法,主要用来对实例对象进行初始化用的

定义一个含有__init__函数的ATM类
class ATM():
    # 类属性 -- 变量
    info = "银行ATM存取款机"
    country = "中国"
    total_balance = 50000
    #方法
    def __init__(self,bank,area,balance):
        print("this is init")
        self.bank = bank
        self.area = area
        self.balance = balance
    def store_money(self,balance):
        self.balance += balance
        print(f"存入金额{balance}元,ATM余额为{self.balance}元")

实例化对象 自动执行__init__函数
__init__函数需要参数,实例化的时候就需要传递参数,不需要的话实例化的时候就不用转递参数
一般用在实例对象的初始化

使用:

a1 = ATM("建行","长沙",50000)
print(type(a1))
print(f"a1的实例空间__dict__是:{a1.__dict__}")
print(a1.info)

结果:

this is init
<class '__main__.ATM'>
a1的实例空间__dict__是:{'bank': '建行', 'area': '长沙', 'balance': 50000}
银行ATM存取款机

self介绍以及使用

方法的第一个参数常常被命名为 self。 这也不过就是一个约定: self 这一名称在 Python 中绝对没有特殊含义。

定义类,

# self代表实例自己
class A:
    # def info(self):
    #     print(f"self.name is {self.name}")
    # self也可以改成其他的
    def info(a1):
        print(f"self.name is {a1.name}")
a1 = A()
a2 = A()
a1.name = "name_a1"
a2.name = "name_a2"
a1.country = "China"
a1.info()
print(f"a1的类空间是 {a1.__dict__}")
#a1.info 会自动把a1转送给info()函数了
# 底层解释器  --》 A.info(a1)
a2.info()
# 用类调用的时候,需要传入一个实例
A.info(a1)

元类

在Python中,元类(metaclass)是一种特殊的类,用于创建其他类。元类提供了一种方式来定义类的创建方式、行为和属性。

元类可以在定义类时进行指定,通过在类定义中使用 metaclass 关键字参数来指定元类。当使用元类创建类时,元类的 __new__() 方法会被调用,并返回一个新的类对象。

下面是一个简单的示例,演示了元类的使用:

class MyMeta(type):
    def __new__(cls, name, bases, attrs):

        # 修改类属性

         attrs['new_attribute'] = 'New Attribute'

        # 创建新的类对象

         new_class = super().__new__(cls, name, bases, attrs)
         return new_class

class MyClass(metaclass=MyMeta):
    attribute = 'Attribute'

obj = MyClass()
print(obj.attribute)  
print(obj.new_attribute) 
-------------------------------------------------------
Attribute
New Attribute

在上面的例子中,定义了一个元类 MyMeta,它继承自内置的 type 类。在元类的 __new__() 方法中,我们可以对类属性进行修改或添加新的属性。在这个例子中,我们添加了一个新的属性 new_attribute

然后,使用 metaclass 关键字参数将元类 MyMeta 应用于类 MyClass 的定义中。这样,在创建 MyClass 时,元类的 __new__() 方法会被调用,并返回一个新的类对象。

通过实例化 MyClass,我们可以访问元类添加的属性 new_attribute,以及原来定义的属性 attribute

需要注意的是,元类是一种高级特性,通常在特定的情况下使用,例如创建框架、库或实现特定的编程模式。在大多数情况下,使用普通的类定义就足够满足需求,而无需使用元类。

总结:元类是一种特殊的类,用于创建其他类。通过定义元类,并将其应用于类的定义中,可以定制类的创建方式和行为。元类可以通过 __new__() 方法来修改类属性,并返回一个新的类对象。但是,元类是一种高级特性,通常在特定场景下使用,而大多数情况下,使用普通的类定义就可以满足需求。

抽象基类

在Python 3中,抽象基类是一种特殊的类,用于定义接口或协议,并强制子类实现这些接口或协议中的方法。抽象基类提供了一种方式来定义类的共同特征,以及规定了子类必须实现的方法。

要创建抽象基类,可以使用abc模块中的ABC类和abstractmethod装饰器。ABC类是一个抽象基类,而abstractmethod装饰器用于将方法标记为抽象方法,即需要子类实现的方法。

下面是一个简单的示例,演示了抽象基类的定义和使用:

from abc import ABC,abstractmethod
class Animal(ABC):
    def breath(self):
        pass

    @abstractmethod
    def eat(self):
        pass

class Dog(Animal):
    def eat(self):
        print("this is eat")

dog = Dog()
dog.eat()
------------------------------
this is eat

在上述示例中,抽象基类 Animal 定义了 eat() 方法作为接口,要求所有子类实现该方法。Dog 类作为 Animal 的子类,实现了 eat() 方法,因此可以正常创建 Dog 类的对象并调用 eat() 方法。

需要注意的是,无法直接创建抽象基类的实例。尝试实例化抽象基类会引发 TypeError。抽象基类的主要目的是定义规范和约束,以确保子类实现特定的接口。

抽象基类提供了一种机制,可以在设计中使用接口和多态性的概念。它可以帮助开发人员定义和维护类之间的一致性,并确保在子类中实现了必要的方法。

新式类和经典类

在 Python 2.x 版本中,有两种不同的类类型:经典类(Classic Class)和新式类(New-style Class)。它们之间存在一些继承、方法解析顺序以及特殊方法处理方面的差异。

经典类是在 Python 2.x 之前的版本中引入的类定义方式。如果一个类没有显式继承自 object,那么它就是一个经典类。例如:

class ClassicClass:
    pass

新式类是在 Python 2.x 引入之后的版本中的一种改进。新式类的定义方式是继承自 object,或者是继承自其他已经继承自 object 的新式类。例如:

class NewStyleClass(object):
    pass

在 Python 3.x 版本中,默认所有的类都是新式类,因此不再需要显式继承自 object

新式类相比经典类引入了一些新的特性和改进,包括:

  1. 方法解析顺序(Method Resolution Order,MRO):新式类使用 C3 算法来确定方法的解析顺序,而经典类使用深度优先搜索。这影响到多重继承时方法的查找顺序。

  2. 属性访问:新式类支持属性访问的特殊方法 __getattribute____setattr__,可以更细致地控制属性的获取和设置过程。

  3. 描述符(Descriptors):新式类引入了描述符协议,允许自定义属性访问和修改的行为。

  4. 继承的改进:新式类对多重继承和超类调用的处理更加一致和合理。

需要注意的是,Python 3.x 中只有新式类,因此新的类定义不需要显式继承自 object

综上所述,新式类是 Python 2.x 引入的一种改进的类定义方式,具有更好的特性和方法解析顺序。而经典类是在 Python 2.x 之前的版本中使用的类定义方式。在 Python 3.x 中,默认所有的类都是新式类。

区别

主要区别如下:

  1. 方法解析顺序(Method Resolution Order,MRO):
    • 经典类使用深度优先搜索来确定方法的解析顺序,即从左到右,深度优先地搜索父类。这可能导致潜在的方法冲突和不一致的行为。
    • 新式类使用 C3 算法来确定方法的解析顺序,保证了更一致和合理的方法查找顺序。它会考虑继承图中的线性化顺序,避免了深度优先搜索的问题。
  2. 属性访问:
    • 新式类引入了特殊方法 __getattribute____setattr__,允许开发者更细致地控制属性的获取和设置过程。这些方法提供了更灵活的属性访问机制。
    • 经典类没有这些特殊方法,属性访问相对较简单,没有同样的细粒度控制。
  3. 描述符(Descriptors):
    • 新式类引入了描述符协议,允许开发者自定义属性的访问和修改行为。通过描述符,可以在类的属性上定义额外的行为,如验证、计算或转换。
    • 经典类没有描述符协议,无法实现类似的属性访问和修改行为的自定义。
  4. 继承的改进:
    • 新式类对多重继承的处理更一致和合理。它使用 C3 算法来解决方法解析顺序问题,避免了经典类中可能出现的方法冲突和不一致性。
    • 经典类在多重继承时使用深度优先搜索,可能导致意外的方法调用和不符合预期的行为。
  5. 默认基类:
    • 在 Python 2.x 中,如果一个类没有显式继承自 object,那么它是一个经典类。
    • 在 Python 3.x 中,所有的类都默认继承自 object,因此都是新式类。不再需要显式继承自 object
  6. 类型区别
    • 经典类通过Type函数查看到的类型都是instance,实例和类的关系只能通过__class__属性获取
    • 新式类 通过type函数就可以它属于哪个类

需要注意的是,Python 3.x 中只有新式类,因此不再存在新式类和经典类之间的差异。以上是在 Python 2.x 版本中新式类和经典类之间的主要区别。

C3算法

C3算法是一种用于确定方法解析顺序(Method Resolution Order,MRO)的算法。在多重继承的情况下,确定方法的调用顺序非常重要,以避免方法冲突和不一致的行为。

C3算法使用拓扑排序的方式来解决方法解析顺序问题。它根据一组继承关系构建一个有向无环图(DAG),并按照特定的规则进行排序,以确定方法的调用顺序。

以下是C3算法的主要步骤:

  1. 创建一个空列表,称为"合并顺序"(merge order)。
  2. 将所有类的继承顺序(包括自身类和父类)按照从左到右的顺序,依次添加到"合并顺序"中。
  3. 从"合并顺序"中选择第一个类(当前类),并检查它是否是其他类的直接父类。
  4. 如果当前类是其他类的直接父类,则将它从"合并顺序"中移除,继续下一步。
  5. 如果当前类不是其他类的直接父类,则保留当前类,将其从所有其他类的继承顺序中移除,并继续下一步。
  6. 重复步骤3到步骤5,直到"合并顺序"中不再有类剩余。
  7. 返回"合并顺序"作为最终的方法解析顺序。

C3算法的目标是找到一个合理的方法解析顺序,它考虑了继承关系的线性化顺序,并且能够解决潜在的方法冲突。C3算法保持了继承关系的一致性,确保了方法的调用顺序符合预期。

在Python中,新式类使用C3算法来确定方法解析顺序,而经典类使用深度优先搜索。C3算法的引入使得多重继承的方法解析顺序更加一致和可预测。

实例

class A:
    def test(self):
        print("from A")

class B(A):
    pass
    # def test(self):
    #     print("from B")


class C(A):
    def test(self):
        print("from C")


class D(B):
    pass
    # def test(self):
    #     print("from D")

class E(C):
    def test(self):
        print("from E")

class F(D,E):
    pass
     # def test(self):
     #     print("from F")

f = F()
f.test()

在用python3和python2运行上面代码,会出现不同的结果python3会显示from E,而python2会显示from A.

方法

在Python 3中,类的方法有三种类型:实例方法(instance methods)、静态方法(static methods)和类方法(class methods)。

  1. 实例方法(Instance Methods)

    • 实例方法是最常用的方法类型,它们与类的实例相关联。
    • 实例方法的第一个参数通常被命名为 self,它引用调用该方法的实例本身。
    • 实例方法可以访问和操作实例属性,并可以调用其他实例方法和类方法。
    • 在类中定义方法时,默认情况下它们都是实例方法。
    • 示例:
      class MyClass:
          def instance_method(self, arg):
      
              # 访问实例属性
      
              self.attribute = arg
              return self.attribute
      
      # 创建实例并调用实例方法
      
      obj = MyClass()
      result = obj.instance_method(10)
      
  2. 静态方法(Static Methods)

    • 静态方法是与类相关联的普通函数,它们与类的实例无关。
    • 静态方法使用 @staticmethod 装饰器进行声明,并不需要额外的参数引用实例或类。
    • 静态方法通常用于执行与类相关的操作,但不需要实例的上下文。
    • 在静态方法内部,无法使用 self 关键字访问实例属性,也无法使用 cls 访问类属性。
    • 示例:
      class MyClass:
          @staticmethod
          def static_method(arg):
      
              # 执行与类相关的操作
      
              return arg
      
      # 调用静态方法
      
      result = MyClass.static_method(5)
      
  3. 类方法(Class Methods)

    • 类方法是在类级别上操作类属性和方法的方法。
    • 类方法使用 @classmethod 装饰器进行声明,并接受一个额外的参数通常被命名为 cls,它引用类本身而不是实例。
    • 通过类方法,您可以访问和修改类属性,以及调用其他类方法。
    • 示例:
      class MyClass:
          class_attribute = 0
      
          @classmethod
          def class_method(cls, arg):
      
              # 访问类属性
      
              cls.class_attribute += arg
              return cls.class_attribute
      
      # 调用类方法
      
      result = MyClass.class_method(5)
      
class Person():
    # 实例属性
    specials = "animal"

    def __init__(self,name):
        self.name = name
    #普通方法(实例方法) 第一个参数 --self --代表实例对象
    def normal_method(self):
        print("normal:")
        print(self.name)

    #类方法 使用classmethod装饰的方法就是类方法
    #第一个参数 ——cls 传递是类,也不代表实例。
    @classmethod
    def class_method(cls,name):
        print("classmethod:")
        print(type(cls))
        print(name,cls.specials)

    #静态方法   使用staticmethod装饰的方法就是静态方法
    #第一个参数可有可无(就是普通参数),
    @staticmethod
    def static_method(name):
        print("state_method")
        print(name,Person.specials)

p1 = Person("sc")
p1.normal_method()
p1.class_method("sc2")
p1.static_method("sc3")

Person.normal_method(p1)
Person.class_method("sc4")
Person.static_method("sc5")

这些不同类型的方法为您提供了不同的功能和灵活性,使您能够根据需要在类中定义和使用不同类型的方法。

区别

下面是关于实例方法、静态方法和类方法的详细区别:

  1. 实例方法

    • 与类的实例相关联,通过实例调用。
    • 第一个参数通常被命名为 self,它引用调用该方法的实例本身。
    • 可以访问和操作实例属性,并可以调用其他实例方法和类方法。
    • 实例方法可以访问和修改实例的状态,并且可以通过 self 访问实例的属性。
    • 示例方法可以被继承,并可以重写以提供不同的实现。
  2. 静态方法

    • 与类相关联,与类的实例无关。
    • 使用 @staticmethod 装饰器进行声明,不需要额外的参数引用实例或类。
    • 通常用于执行与类相关的操作,但不需要实例的上下文。
    • 无法访问实例属性或方法,也无法访问类属性或方法。
    • 静态方法在类级别上提供了一种组织相关操作的方式,但与类的实例无关。
    • 无法通过继承和重写来改变静态方法的行为。
  3. 类方法

    • 在类级别上操作类属性和方法。
    • 使用 @classmethod 装饰器进行声明,并接受一个额外的参数通常被命名为 cls,它引用类本身而不是实例。
    • 可以访问和修改类属性,并可以调用其他类方法。
    • 类方法可以被继承,并可以通过子类进行重写。
    • 类方法常用于在创建实例之前对类属性进行修改或进行类属性的计数。
    • 通过 cls 参数,类方法可以在方法内部引用类本身并操作类别的属性和方法。

总结:

  • 实例方法用于操作实例属性和方法,可以访问和修改实例状态,并且可以通过 self 访问实例属性。
  • 静态方法是与类相关但与实例无关的方法,无法访问实例属性或方法,也无法访问类属性或方法。
  • 类方法用于在类级别上操作类属性和方法,通过额外的 cls 参数引用类本身,可以访问和修改类属性,并可以调用其他类方法。

根据您的需求和设计的目标,选择适当的方法类型,以便在类中定义和使用不同类型的方法。

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
### 回答1: 魔术方法是Python中的特殊方法,它们以双下划线开头和结尾,例如__init__、__str__、__add__等。这些方法可以在类的实例化、运算符重载、属性访问等方面提供特殊的行为。 __init__方法是一个特殊的构造函数,用于初始化类的实例。__str__方法用于返回对象的字符串表示形式,可以通过print函数输出。__add__方法用于重载加法运算符,可以实现自定义的加法操作。其他常用的魔术方法还包括__eq__、__lt__、__gt__等,用于比较运算符的重载。 学习魔术方法可以让我们更好地理解Python面向对象编程的特性,提高代码的可读性和可维护性。 ### 回答2: 魔术方法是Python中最有趣且也是最强大的概念之一。魔术方法(也称为特殊方法或双下划线方法)是一些特殊的方法,它们以双下划线(__)开头和结尾,并具有特定的名称。 这些特殊方法可以为我们提供许多有用的功能,例如重载操作符,处理类的属性,实现自定义迭代器,使用描述符等。 下面是一些常见的魔术方法: __init__:这是最常见的魔术方法。当创建一个实例时,它会被自动调用。它用于初始化对象的属性。 __str__:当你想要将一个对象转换成字符串时,这个方法会被调用。如果你不指定__str__方法,Python默认会使用对象的类名和内存地址来表示对象。 __repr__:这个方法和__str__方法类似,也是用于将对象转换成字符串。但是__repr__方法在调试时有很大的作用,因为它返回的字符串可以用来唯一地标识对象。 __len__:这个方法可以返回对象的长度。例如,如果你想获取一个字符串的长度,你可以使用len("hello"),在底层,它实际上是调用了字符串对象的__len__方法。 __getattr__和__setattr__:这些方法允许你动态地获取和设置对象的属性。当你访问一个不存在的属性时,__getattr__方法会被调用。当你设置一个属性时,__setattr__方法会被调用。 __call__:这个方法允许你将对象作为函数调用。当你调用一个对象时,Python实际上是在调用对象的__call__方法。 除了上面列举的方法,还有许多其他的魔术方法,例如__cmp__,__hash__,__iter__等等。学习这些魔术方法将使你能够更好地理解Python的面向对象编程模型。 总之,学习和理解魔术方法是Python面向对象编程中的一个关键概念,因为它们可以帮助你实现更加灵活和强大的代码。如果你想成为一名Python高手,那么深入学习魔术方法是不可避免的。 ### 回答3: Python中的“魔术方法”指的是每个类中定义的特殊方法,它们以双下划线(__)开头和结尾,并且有着特定的用途。通过使用这些魔法方法,我们可以自定义类的行为,并为程序提供更高级别的功能。 以下是Python中常用的一些魔术方法: 1. __init__:这是最常用的魔术方法之一,它用于初始化一个类的对象,以及定义类的属性和方法。 2. __str__:此方法用于返回对象的字符串表示形式,类似于Java中的toString()方法。 3. __repr__:与__str__类似,但是返回的是对象的“官方”字符串表示形式,通常用于调试和开发。 4. __getattr__:当试图访问一个不存在的属性时,此方法被调用。 5. __setattr__:当尝试设置类的属性时,此方法被调用。 6. __delattr__:当尝试删除类的属性时,此方法被调用。 7. __call__:将对象作为函数调用时,此方法被调用。 8. __len__:返回对象的长度。 9. __getitem__:允许通过索引访问对象的元素。 10. __setitem__:允许通过索引设置对象的元素。 11. __delitem__:允许通过索引删除对象的元素。 通过了解和使用这些魔术方法,我们可以编写出更高效、更灵活、更具可读性的Python代码,并且实现类似于内置类型一样的功能。例如,我们可以实现一个自定义列表,类似于Python的list类型,然后使用上述魔术方法来访问、设置和删除元素。同时,我们还可以自定义变量和函数的行为,使我们的Python代码变得更具有表现力和弹性。 总之,了解和掌握Python的魔术方法是Python编程中必不可少的一部分,对于理解和编写实际应用程序非常有价值。在实践中,我们可以根据实际情况选择恰当的魔术方法,从而创建更灵活、更高效的Python类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哈密猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值