Python学习-面向对象(类)

这篇博客详细介绍了Python的面向对象编程,包括类的引入、定义、属性和方法的查找流程,以及面向对象的三大特性:封装、继承和多态。重点讲解了类变量、实例变量、方法的self参数、特殊方法以及继承、多态的概念和应用。此外,还讨论了封装的作用,如数据安全性、只读属性和属性装饰器。最后,总结了实例属性、类属性、实例方法、类方法、静态方法、私有方法和专有方法的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 了解面向对象

  • Python 就是一个面向对象的编程语言
  • 对象就是内存中用来存储指定数据的一块区间
  • 对象的结构:每个对象当中都保存了3种数据:
    1、id(标识):id是由我们的解析器生成的,在Cython中,id就是对象的内存地址。
    2、type(类型):用来标识当前对象所属的类型,类型就决定了对象有什么功能。
    3、value(值):就是对象中存储的具体数据。
  • 概念:
    (1)类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
    (2)方法:类中定义的函数。
    (3)类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
    (4)数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
    (5)方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
    (6)局部变量:定义在方法中的变量,只作用于当前实例的类。
    (7)实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
    (8)继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
    例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
    (9)实例化:创建一个类的实例,类的具体对象。
    (10)对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

2 类

2.1 类的引入

  • Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。
  • 对象可以包含任意数量和类型的数据。
  • 类(class):简单理解相当于一个图纸,在程序中我们需要根据类来创建对象
  • 学习的都是Python的内置对象,如int() str() list() tuple() set() dict()
  • 类是type类型的对象,定义类实际上就是定义了一个type类型对象
  • 可以通过类来创建一些自定义的对象
  • 使用class 关键字来定义类,语法:
class 类名([父类]):
      代码块

这里类名后面括号里的参数(即父类)可以省略,如果有父类传递过来,那就是继承

  • 类名一般采用驼峰命名法
  • 现在通过MyClass 创建的对象都是空对象
  • 可以向对象中添加变量,对象中添加变量我们称之为属性
    语法: 对象.属性名=属性值
  • 可以用isinstance()函数来检查一个对象是否是另一个类的实例,返回值是True或False
class MyClass():
    pass
print(id(MyClass),type(MyClass))    # 35759912 <class 'type'>
mc=MyClass()    # mc是通过类 MyClass来创建的,mc就是MyClass的实例
print(mc,type(mc))  # <__main__.MyClass object at 0x00000000028C2780> <class '__main__.MyClass'>
r= isinstance(mc,MyClass)
print(r)    # True
mc.name='hello'
print(mc.name)  # hello

2.2 类的定义

  • 类的定义:类和对象都是现实生活中的抽象
  • 实际上,所有的事物有两部分组成:
    1.数据(属性)
    2.行为(方法)
  • 在类中的代码块,我们可以定义变量和函数
  • 类对象创建后,类命名空间中所有的命名都是有效属性名。
  • 在类中定义的变量和函数将会成为所有实例的公共属性和公共方法,任何该类的实例都可以访问。
  • 方法调用和函数调用的区别:
    1.如果是函数调用,有几个形参,就传递几个实参;
    2.如果是方法调用,默认要指定一个形参,所以方法中至少要有一个形参

class Person():
    a=123
    b=456
    def speak(self):
        print('hahah')
# 创建Person的实例
p1=Person()
# 调用类的属性,语法:对象.属性名
print(p1.a,p1.b)  # 123 456
# 调用类的方法,语法:对象.方法名()
p1.speak()  #  hahah

2.3 属性和方法的查找流程

  • 当我们调用一个对象的属性时,解析器会先在当前的对象中寻找是否有该属性(方法):
    (1)如果有,则直接返回当前的对象的属性值(方法返回值);
    (2)如果没有,则去当前对象的类对象中去寻找,如果有则返回类对象的属性值(方法返回值),如果没有就报错。
class Text():
    c='haha'
    def spe(self):
        print('123')
p2=Text()
p2.c='wowo'
print(p2.c)     # 结果为 wowo,而不是 haha
  • 类对象和实例对象中都可以保存属性(方法),具体要保持到哪里,看实际需求:
    (1)如果这个属性(方法)是所以的实例共享的,则应该将其保存到类对象中;
    (2)如果这个属性(方法)是某个实例独有的,则应该保存到实例对象中,别的实例访问不到;
    (3)一般情况下,属性保存到实例对象中,而方法需要保存到类对象中。

2.4 方法的默认形参 self

  • self在定义时需要定义,但是在调用时会自动传入。
  • self的名字并不是规定死的,但是最好还是按照约定和规范用 self
  • self总是指调用时的类的实例。
  • 注意:
    (1)类方法必须包含参数 self, 且为第一个参数
    (2)在定义类的方法时,不能直接调用方法外的变量,这是和函数嵌套不同的地方。
  • 如果要调用,可以使用 self.属性名 的方式
class StrPrint():
    str='hello'
    def pri(self):
        print(self.str)
a=StrPrint()
b=StrPrint()
a.str='hey'
b.str='你好'
# 哪个实例调用,self就是哪个实例的对象
a.pri()     # 输出 hey
b.pri()     # 输出 你好
# self 代表的是类的实例,代表当前对象的地址
print(a.pri)    # 输出 <bound method StrPrint.pri of <__main__.StrPrint object at 0x00000000026D2668>>

2.5 特殊方法

  • 在类中可以定义一些特殊的方法,形如__mmm__,注意前后都是双下划线,比如初始化方法:init(self),该方法在类实例化时会自动调用。
  • 这些特殊方法不需要我们自己调用,创建实例时就已经调用,有多少个实例就会被调用多少次。
  • 类中的代码执行顺序(例如):先执行类里面的代码块,再执行__init__部分的代码块。
  • 当一个类创建了多个实例时,类里面的代码只执行一次,而特殊方法则会根据实例的个数执行多次。
class StrPrint():
    print('hello')
    def __init__(self):
        print('hahhah')
p1=StrPrint()
p2=StrPrint()
p3=StrPrint()

执行结果:

hello
hahhah
hahhah
hahhah
  • 类的基本结构:
class ClassName([父类]):
      公共属性1
      公共属性2
      ……
      # 对象的初始化方法
      def __init__(self,...):
          代码块
      # 其他的方法
      def methodname(self,...)
          代码块
       ……
  • 定义一个车的类
    属性:name、color
    方法:run()
class Car():
    name=''
    color=''
    def __init__(self,name,color):
        self.name=name
        self.color=color
    def run(self):
        print('The car is running.')
car=Car()

3 面向对象的三大特性

  • 面向对象的三大特性:
    1.封装:确保对象中数据的安全
    2.继承:保证对象的扩展性
    3.多态:一个对象可以以不同的形态去呈现

3.1 封装

3.1.1 封装的引入

  • 出现封装的原因:我们需要一种方式来增强数据的安全性
  • 要求:1、属性不能随意修改;2、属性不能改为任意的值
  • 封装是指隐藏对象中一些不希望被外部所访问到的属性或方法:
    1、将对象属性名修改为一个外部不知道的名字:
class Dog():
    def __init__(self,name):
        self.hidden_name=name
    def speak(self):
        print('大家好,我是%s'%self.hidden_name)
d=Dog('大狼狗')
d.name='大柴狗'
d.speak()   # 输出结果为:大家好,我是大狼狗   说明类的属性值没有被修改

2、外部使用getter()和setter()方法访问到类的属性并修改:
getter()获取对象中指定的属性
setter()用来设置对象指定的属性

class Dog():
    def __init__(self,name):
        self.hidden_name=name
    def speak(self):
        print('大家好,我是%s'%self.hidden_name)
    # get_name()用来获取类的name属性值
    def get_name(self):
        return self.hidden_name
    # set_name()用来修改类的name属性值
    def set_name(self,name):
        self.hidden_name=name
d=Dog('大狼狗')
print(d.get_name())     # 输出:大狼狗
d.set_name('二哈')
print(d.get_name())     # 输出:二哈
d.speak()       # 输出:大家好,我是二哈

3.1.2 封装的作用

  • 使用封装,确实增加了类的定义的复杂程度,但也确保了数据的安全:
    (1)隐藏属性名,是调用者无法随意修改类的属性。
    (2)增加getter()和setter()方法,控制属性是否是只读的。
    (3)使用setter()设置属性,可以验证数据的准确性。
    (4)使用getter()方法获取属性,使用setter()方法设置属性可以在读取属性和修改属性的同时做一些其他的处理。
class Dog():
    def __init__(self,name,age):
        self.hidden_name=name
        self.hidden_age=age
    def speak(self):
        print('大家好,我是%s'%self.hidden_name)
    # get_name()用来获取类的name属性值
    def get_name(self):
        return self.hidden_name
    # set_name()用来修改类的name属性值
    def set_name(self,name):
        self.hidden_name=name
    def get_age(self):
        print('用户读取了属性...')
        return self.hidden_age
    def set_age(self,age):
        print('用户修改了属性。。。')
        if age > 0:
            self.hidden_age= age
d=Dog('大狼狗',2)
d.get_age()
d.set_age(3)

3.1.3 基本属性、隐藏属性、私有属性

  • 其属性名不包含任何下划线的属性为基本属性。
  • 双下划线开头的属性,形如__***,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问。
  • 其实隐藏属性只不过是Python自动为属性改了一个名字 --> _类名__属性名,例如 __name --> _Person__name,然而这种方式实际上依然可以在外部访问,所以这种方式我们一般不用。因此我们使用单个下划线_开头定义私有属性,一般情况下,使用_开头的属性都是私有属性,没有特殊情况下不要修改私有属性。
    示例1:
class Person():
    def __init__(self,name):
        # 定义隐藏属性
        self.__name=name
    def get_name(self):
        return self.__name
    def set_name(self,name):
        self.__name=name
p1=Person('张三')
# p1.__name  会报错 AttributeError: 'Person' object has no attribute '__name',隐藏属性只能在类的内部访问,无法通过对象访问。
print(p1._Person__name)   #  通过这种方式依然可以获取类的属性,输出结果为:张三

示例2:

class Person():
    def __init__(self,name):
        # 定义私有属性
        self._name=name
    def get_name(self):
        return self._name
    def set_name(self,name):
        self._name=name
p2=Person('李四')
# print(p2._Person_name)    # 访问不了,会报错AttributeError: 'Person' object has no attribute '_Person_name'
print(p2._name)   # 访问私有属性,输出结果为:李四,没有特殊情况下不要修改私有属性

3.1.4 类的装饰器

  • 类的装饰器:扩展方法的功能
  • property装饰器:
    (1)我们可以使用 @property装饰器来创建只读属性。
    (2)@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。
    (3)@property装饰器应用场景:当定义的方法内容比较庞大,且不需要传递参数,同时方法只需要一个返回值的时候。
class Person():
    def __init__(self,name):
        self._name=name
    # getter()方法
    @property
    def get_name(self):
        print('getter()方法执行了')
        return self._name
    # getter 和 setter 方法的定义:  @方法名.getter  和  @属性名.setter
p3=Person('王五')
# 调用Person类的get_name()方法,虽然是在调用方法,但加了@property装饰器之后,方法已经转换为属性,访问时方法名后面不需要加括号
print(p3.get_name)  # 输出结果为: getter()方法执行了   王五

3.2 继承

  • 继承:子类会继承父类的所有属性和方法,包括特殊方法。
  • Python同样支持类的继承,如果一种语言不支持继承,类就没有什么意义了。类提高了代码的复用性。
  • 在创建类的时候,如果省略了父类,则默认父类为object,object是所有类的父类。
  • 基类(BaseClassName)必须与派生类定义在一个作用域内。
  • super()函数可以获取当前类的父类,并且通过super()返回对象调用父类方法时,不需要传递self。

3.2.1 单继承

派生类(derivelclass)的定义语法如下:

class DerivelclassName(BaseClassName1):
      代码块

除了类,还可以用表达式,基类定义在另一个模块中时,这一点非常有用:

class DerivedClassName(modname.BaseClassName):
# 定义基类
class PersonInformation():
    # 定义基本属性
    name=''
    age=0
    # 以单下划线开头,定义私有属性,私有属性在类外部无法直接进行访问
    _weight =0
    # 定义初始化方法(构造方法)
    def __init__(self,name,age,weight):
        self.name= name
        self.age=age
        self._weight=weight
    def print_infor(self):
        print('name:%s age:%d  weight:%d'%(self.name,self.age,self._weight))
# 单继承示例:
class PersonOne(PersonInformation):
    # 添加新的基础属性
    city=''
    def __init__(self,name,age,weight,city):
        调用父类的构造方法,
        PersonInformation.__init__(self,name,age,weight)  # 等价于super().__init__(name,age,weight)
        self.city=city
    # 重写父类的方法
    def print_infor(self):
        print('name:%s age:%d  weight:%d  city:%s'%(self.name,self.age,self._weight,self.city))
p1=PersonOne('李四',23,55,'hangzhou')
p1.print_infor()     # 输出结果为  name:李四 age:23  weight:55  city:hangzhou
issubclass(子类名,父类名)函数:检查一个类是不是另一个类的子类,如果是 则返回True,如果不是 则返回false
print(issubclass(PersonOne,PersonInformation))      # 输出:True
print(issubclass(PersonOne,object))      # 输出:True
print(issubclass(PersonInformation,object))      # 输出:True

3.2.2 多继承

  • Python同样有限的支持多继承形式。多继承的类定义形如:
class DerivedClassName(Base1,Base2,Base3,...):
    代码块
  • 注意:圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
# 定义另一个基类
class JobInformation():
    job=''
    _salary=0
    def __init__(self,job,salary):
        self.job=job
        self._salary=salary
    def print_infor(self):
        print('job:%s  salary:%d'%(self.job,self._salary))
# 多重继承示例:
class PersonTwo(JobInformation,PersonInformation):
    def _init__(self,name,age,weight,job,salary):
        # 调用父类的构造方法
        PersonInformation.__init__(self,name,age,weight)
        JobInformation.__init__(self,job,salary)
p2=PersonTwo('teacher',6000)
p2.print_infor()  # 方法名同,默认调用的是在括号中排前地父类的方法,输出结果为: job:teacher  salary:6000
# 类名.__bases__  这个属性可以获取当前类的所有父类
print(PersonTwo.__bases__)      # 输出结果:  (<class '__main__.JobInformation'>, <class '__main__.PersonInformation'>)

3.2.3 方法重写

  • 如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法。
  • 如果在子类中有何父类重名的方法,则通过子类的实例去调用该方法时,会先调用子类的方法,这个特点我们称之为方法的重写(覆盖)。
  • 方法的查找顺序:先在子类找,没有找到则去继承的父类找,以此类催,直到找到object,如果依然没有找到则会报错。
# 定义父类
class Parent():
    def myMethod(self):
        print('调用父类方法')
# 定义子类
class Child(Parent):
    def myMethod(self):
        print('调用子类方法')
# 创建子类实例
c=Child()
# 子类调用重写方法
c.myMethod()    # 输出结果为: 调用子类方法

3.3 多态

  • 例如:len()函数,len()函数的参数可以是列表、字符串等等,其用法就是一种多态的表现。
  • len()函数之所以可以获取不同对象的长度,是因为对象中有一个特殊方法 len

4 属性和方法(总结)

4.1 实例属性和类属性

  • 实例属性:通过实例对象添加的属性属于实例属性
  • 实例属性只能通过实例对象来访问和修改,类对象无法访问修改
  • 类属性:直接在类中定义的属性是类属性.
  • 类属性可以通过类或类的实例访问到。但是类属性只能通过类对象来修改,无法通过实例对象修改。
class ClassName():
    # 定义类属性
    num=10
a=ClassName()
# 定义实例属性
a.str='haha'
a.num=20
print('类的num属性值:',ClassName.num)        # 输出结果:  类的num属性值: 10
print('实例的num属性值:',a.num)       # 实例的num属性值: 20
print('实例的str属性值:',a.str)       # 实例的str属性值: haha
ClassName.num=30
print('类的num属性值:',ClassName.num)        # 输出结果:  类的num属性值: 30
print('实例的num属性值:',a.num)       # 实例的num属性值: 20

4.2 实例方法和类方法

  • 实例方法:在类中定义,以self为第一个参数的方法都是实例方法。
  • 实例方法在调用时,Python会将调用对象以self传入。
  • 实例方法可以通过类实例和类去调用:
    (1)当通过实例调用时,会自动将当前调用对象作为self传入。
    (2)当通过类调用时,不会自动传递self,我们必须手动传递当前实例对象。
  • 类方法:在类的内容以@classmethod 来修饰的方法属于类方法。
  • 类方法第一个参数是cls,也会自动被传递。cls就是当前的类对象。
  • 类方法和实例方法的区别:实例方法的第一个参数是self,类方法的第一个参数是cls。
  • 类方法可以通过类去调用,也可以通过实例调用。
class ClassName():
    # 定义类属性
    num=10
    # 定义实例方法,第一个参数为self,会自动传入
    def test(self):
        print('这是实例方法。')
    # 定义类方法
    @classmethod
    def test1(cls):
        print('这是类方法。')
        # 类方法和实例方法调用属性的方式差不多
        print(cls.num)
a=ClassName()
# a.test()  等价于  ClassName.test(a)
a.test()    # 输出结果为:这是实例方法。
ClassName.test(a)   # 括号里的a是当前的实例对象,输出结果为:这是实例方法。
# a.test1() 等价于  ClassName.test1()
a.test1()       # 输出结果为:这是类方法。
ClassName.test1()       # 输出结果为:这是类方法。

4.3 静态方法

  • 静态方法:在类中用@staticmethod来修饰的方法属于静态方法。
  • 静态方法不需要指定任何的默认参数,并且静态方法可以通过类和实例调用。
  • 静态方法,基本上是一个和当前类无关的方法,它只是一个保存到当前类中的函数。
  • 静态方法一般都是些工具方法,和当前类无关。
class A():
    @staticmethod
    # 静态方法需要指定任何的默认参数,包括实例方法的self和类方法的cls
    def statmeth():
        print('这是静态方法。')
a=A()
# 以下两种调用方式的输出结果均为:  这是静态方法。
a.statmeth()
A.statmeth()

4.4 类的私有方法和专有方法

  • __private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。
    调用方式:self.__private_methods
    示例:
class Per():
    def __init__(self,name,age):
        self.name=name  # 公共属性
        self.__age=age   # 私有属性
    def public(self):      # 公共方法
        print('这是公共方法。')
    def __private(self):
        print('这是私有方法。')
p=Per('张三',23)
p.public()      # 正常输出
p.__private() # 会报错 AttributeError: 'Per' object has no attribute '__private'
  • 类的专有方法:
    init : 构造函数,在生成对象时调用
    del : 析构函数,释放对象时使用
    repr : 打印,转换
    setitem : 按照索引赋值
    getitem: 按照索引获取值
    len: 获得长度
    cmp: 比较运算
    call: 函数调用
    add: 加运算
    sub: 减运算
    mul: 乘运算
    truediv: 除运算
    mod: 求余运算
    pow: 乘方

4.5 运算符重载

Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:

class Vector:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __str__(self):
        return 'Vector (%d, %d)' % (self.a, self.b)
    def __add__(self, other):
        return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2, 10)
v2 = Vector(5, -2)
# 相加过程:  Vertor(  2+5 , 10+(-2) )
print(v1 + v2)      # 输出结果为:Vector (7, 8)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值