Python中的类和对象

本文最后更新于 2026年8月23日

未完待续

1.定义类和对象

python3中的根类,有且只有一个:object(小写)

⚠️大写的Object不存在于python内置中,是自己定义的,为防止混淆不建议使用

__bases__是python中一个内置的变量,代表基类

python中,通过class关键字定义一个类,类名通常采用大驼峰式的命名规范

# 隐式继承,python3自动处理
class Cat:
    pass

# 显式继承
class Dog(object):
    pass

if __name__ == '__main__':
    print( Cat.__bases__ ) # (<class 'object'>,)
    print( Dog.__bases__ ) # (<class 'object'>,)

类名加小括号,会调用类构造函数,便生成了一个类的对象

cat = Cat()

2.类的成员

2.1 构造方法

  • python初始化类的对象,使用__init__()这个特殊方法作为类的构造方法,创建对象时会被自动调用。
  • python构造方法不支持重载,不能弄多个__init__(),否则最后一个覆盖前面的。

构造方法就是一种实例方法

class Cat:
    '''我是说明文档'''
    def __init__(self):
        print('init')
        print(self)
        print(self.__class__)

if __name__ == '__main__':
    cat = Cat()
    print(cat)
init
<__main__.Cat object at 0x000001982BE1D400>
<class '__main__.Cat'>
<__main__.Cat object at 0x000001982BE1D400>

__init__()方法支持自定义参数

class Cat:
    """我是说明文档"""
    def __init__(self, name, color):
        print('init')
        self.name = name
        self.color = color

if __name__ == '__main__':
    cat = Cat(name='yuanbao', color='orange')

2.2 实例方法

python的实例方法第一个参数必须是当前调用对象(类似Java中的this),名字可以任意取,但是通常都起名叫self代表当前调用对象

实例方法仅有一份,保存在类身上,每个实例都能访问这个方法

.__dict__可以代表类/对象所有变量

class Cat:
    """我是说明文档"""
    def __init__(self, name, color):
        print('init')
        self.name = name
        self.color = color

    def speak(self):
        print('miao ', self.name, self.color)

if __name__ == '__main__':
    cat = Cat(name='yuanbao', color='orange')
    cat.speak() 
    print(Cat.__dict__)
    print(cat.__dict__)
init
miao  yuanbao orange
{'__module__': '__main__', '__firstlineno__': 1, '__doc__': '我是说明文档', '__init__': <function Cat.__init__ at 0x00000222CCAB3240>, 'speak': <function Cat.speak at 0x00000222CCD37C40>, '__static_attributes__': ('color', 'name'), '__dict__': <attribute '__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>}
{'name': 'yuanbao', 'color': 'orange'}

实例方法,也可由类调用,但是本质仍然是实例在调用

class Cat:
    """我是说明文档"""
    def __init__(self, name, color):
        print('init')
        self.name = name
        self.color = color

    def eat(self, food):
        print('eat ', food)

if __name__ == '__main__':
    cat = Cat(name='yuanbao', color='orange')
    cat.eat('fish and mouse')
    Cat.eat(cat, 'can')
init
eat  fish and mouse
eat  can

2.3 类和实例的变量

  • 定义在类以内,直接写的变量,没有self.,就是类属性(类变量),类似Java中的static
  • 通过self.定义的是实例变量,实例变量通常在构造器__init__中声明
  • 实例变量和类变量不同名时,实例变量通过对象.访问,类变量可以通过类.访问或对象.访问
  • 实例变量和类变量同名是合法但是不推荐的,这种情况下,实例和类会各自有一份,对象.访问到的是实例变量,类变量只能通过类.访问。如类变量没有值,实例新增了一个同名属性,会直接绑定在实例,不会通过实例把类的同名属性赋上值

⚠️__init__()外面的同名变量前面没有static且和self.后面的变量名重名时,self.极易被Java程序员误判为是在为外面的变量初始化或赋值,但是这是两个不一样的变量,这种Java的思路,在python中是错误的

例:

  • __init__()里面的重名name,age是实例变量,通过对象.访问
  • __init__()外面的重名name,age是类变量,通过类.访问
  • country变量不重名,可通过对象.访问,也可以通过类.访问
class Stu:
    #类的
    name = 'simaple_name'
    age = 0
    country = 'China'

    def __init__(self, age, name):
        print('init')
        #实例的
        self.age = age
        self.name = name

    def speak(self):
        print(self.age, self.name)


if __name__ == '__main__':
    stu1 = Stu(18, '元宝')
    stu2 = Stu(20, '大黄')
    stu1.speak()
    stu2.speak()
    print('---------------')
    print(Stu.name)
    print(Stu.age)
    print('-------------')
    print(stu1.country)
    print(stu2.country)
    print(Stu.country)
    print(stu1.__dict__)
    print(stu2.__dict__)
    print(Stu.__dict__)
init
init
18 元宝
20 大黄
---------------
simaple_name
0
-------------
China
China
China
{'age': 18, 'name': '元宝'}
{'age': 20, 'name': '大黄'}
{'__module__': '__main__', '__firstlineno__': 1, 'name': 'simaple_name', 'age': 0, 'country': 'China', '__init__': <function Stu.__init__ at 0x00000176C3C23240>, 'speak': <function Stu.speak at 0x00000176C3EA7C40>, '__static_attributes__': ('age', 'name'), '__dict__': <attribute '__dict__' of 'Stu' objects>, '__weakref__': <attribute '__weakref__' of 'Stu' objects>, '__doc__': None}

有时候,一个类存在变量过多,逐一用参数赋值会很复杂,例如:

  • def __str__(self)同样是一个内置函数,类似Java中的toString(),打印对象时转换为字符串
  • 双下划线__开头的变量,属于私有变量,类外不可直接访问
class Stu:

    def __init__(self, name, age, no, score):
        self.name = name
        self.age = age
        self.no = no
        self.__score = score

    def __str__(self):
        return f'{self.name}, {self.age}, {self.no}, {self.__score}'


if __name__ == '__main__':
    stu1 = Stu('liming', 16, 10001, 100)
    print(stu1)
    print(stu1.name)
    print(stu1.age)
    print(stu1.no)
    #print(stu1.__score) ❌错误 AttributeError: 'Stu' object has no attribute '__score'


liming, 16, 10001, 100
liming
16
10001

解决这个问题,就可以__init__()外面给某个对象设置成员变量,例如为stu2动态指定一个变量no

class Stu:

    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.__score = score

    def __str__(self):
        return f'{self.__dict__}'


if __name__ == '__main__':
    stu1 = Stu('liming', 16, 100)
    print(stu1)

    stu2 = Stu('liming', 16, 100)
    stu2.no = '10002'
    print(stu2)
{'name': 'liming', 'age': 16, '_Stu__score': 100}
{'name': 'liming', 'age': 16, '_Stu__score': 100, 'no': '10002'}

上面的写法不推荐,可以采用定义好再去赋值的写法

class Stu:

    def __init__(self):
        self.name = None
        self.age = None
        self.no = None

    def __str__(self):
        return f'{self.__dict__}'


if __name__ == '__main__':
    stu1 = Stu()
    print(stu1)

    stu2 = Stu()
    stu2.age = 12
    stu2.no = '123'
    stu2.name = 'sxh'
    print(stu2)
{'name': None, 'age': None, 'no': None}
{'name': 'sxh', 'age': 12, 'no': '123'}

还可以通过可变参数

class Stu:

    def __init__(self, **kwargs):
        self.name = kwargs.get('name', None)
        self.age = kwargs.get('age', None)
        self.no = kwargs.get('no', None)

    def __str__(self):
        return f'{self.__dict__}'


if __name__ == '__main__':
    stu1 = Stu(name='lzj', age=19, no='10002')
    print(stu1)
    stu2 = Stu(name='xiaohong', age=19 )
    print(stu2)
{'name': 'lzj', 'age': 19, 'no': '10002'}
{'name': 'xiaohong', 'age': 19, 'no': None}

2.4 成员访问限制

在python中,属性可以进行访问权限控制,和Java类似,如果要访问受保护的属性,就定义方法,这是类封装性的体现

  • 受保护的属性 _单个下划线开头,仅仅起到提醒作用,但是不阻止访问,例如_age
  • 私有属性 __双下划线开头,类外不可访问,否则程序运行出错,例如__age
  • 公开访问属性 没有下划线的,无限制
  • 魔法属性 __双下划线开头和结尾,是python内置的,有特殊的含义,不可自行定义,例如__dict__

⚠️ python处理__双下划线的底层实现,是将该变量替换成了_类名__私有变量名,通过将某个私有变量名修改为_类名__私有变量名的形式,可以突破访问控制,这种访问方式极不推荐

例:

class Stu:

    def __init__(self, **kwargs):
        self.name = kwargs.get('name', None)
        self.__age = kwargs.get('age', None)
        self._no = kwargs.get('no', None)

    def __str__(self):
        return f'{self.__dict__}'

    def getAge(self):
        return self.__age


if __name__ == '__main__':
    stu1 = Stu(name='lzj', age=19, no='10002')
    print(stu1)
    print(stu1.name)
    print(stu1.getAge())
    print(stu1._no) #不建议的
    print(stu1._Stu__age) #不建议的
    #print(stu1.__age) #运行出错
{'name': 'lzj', '_Stu__age': 19, '_no': '10002'}
lzj
19
10002
19

2.5 静态方法

上面例子里面出现在类中,参数以self开头的方法,都是实例方法,在python中,还存在静态方法和类方法

在python中,有一种方法,只是出现在类中,但是不依赖于实例和类的普通方法,叫做静态方法,采用@staticmethod装饰器装饰,通常都是作为一种工具方法,除非传入否则不能访问类和实例的成员,可以使用实例名或类名调用,但是参数列表中不能出现selfcls

例:def sum(a, b)就是静态方法

class Stu:

    def __init__(self):
        name = None

    def call(self):
        self.sum(1, 2)
        self.__class__.sum(3, 4)
        Stu.sum(5, 6)

    @staticmethod
    def sum(a, b):
        return a + b


if __name__ == '__main__':
    stu1 = Stu()
    stu1.call()
    stu1.sum(7, 8)
    Stu.sum(9, 10)


2.6 类方法

类方法类似Java中的static方法,python的类方法声明时第一个形参永远是cls代表类本身(不是实例),同时除__new__()以外,所有类方法都要采用@classmethod函数装饰器修饰。

python的类方法既能通过类也能通过实例调用,但是为防混淆不建议用实例调用。

主要用于操作类属性或实现工厂模式。

可直接访问类属性,类方法和静态方法,不能直接操作实例属性或实例方法。

例:

class Stu:

    school = 'bupt'

    def __init__(self):
        name = None

    def call(self):
        pass

    @classmethod
    def classfunc1(cls):
        print(cls.__name__)
        print(cls.school)
        cls.sum(1, 2)
        cls.classfunc2()

    @classmethod
    def classfunc2(cls):
        print('classfunc2')

    @staticmethod
    def sum(a, b):
        return a + b


if __name__ == '__main__':
    Stu.classfunc1()

    s1 = Stu()
    s1.classfunc2()
    s1.__class__.classfunc2()
Stu
bupt
classfunc2
classfunc2
classfunc2

还可以实现工厂方法,例如:

class Person:

    @classmethod
    def create(cls, name):
        return cls(**{'name': name})

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


if __name__ == '__main__':
    p = Person.create('lsj')
    print(p.name)

2.7 @property装饰器

@property是Python的一个内置装饰器,让你可以像访问属性一样调用方法,实现受控的属性访问。

例:age作为只读

class Stu:

    @property
    def age(self):
        return self.__age

    def __init__(self, age):
        self.__age = age
        pass

if __name__ == '__main__':
    print(Stu.age) #<property object at 0x000001D82AA20D10>

    s1 = Stu(29)
    print(s1.age) # 29
    s2 = Stu(34)
    print(s2.age) #34

例:radius可读可写

class Circle:
    def __init__(self, radius):
        print('init ', radius)
        self._radius = radius

    @property
    def radius(self):
        print('获取半径')
        return self._radius

    @radius.setter
    def radius(self, value):
        print('设置半径(带验证)')
        if value < 0:
            raise ValueError("半径不能为负数")
        self._radius = value

    @radius.deleter
    def radius(self):
        print('删除半径')
        del self._radius


# 使用
c = Circle(5)

c.radius = 10

print(c.radius)

# c.radius = -1  # ValueError: 半径不能为负数

del c.radius
init  5
设置半径(带验证)
获取半径
10
删除半径

2.8 魔术方法

魔术方法,是类中以双下划线起始和结尾的特殊方法,不需手动调用,只要将这种方法准备好,在特定场景下,python会自动调用

  • __call__() 让实例对象被当作函数调用

    class Person:
    
        def __init__(self, name):
            self.name = name
    
        def __call__(self, age):
            self.age = age
            return self
    
    
    if __name__ == '__main__':
        p = Person('lsj')
        q = p(22)
        print(q.name) #lsj
        print(q.age) #22
  • __repr__()

    __str__()类似,同时定义时,__str__()优先

2.9 @dataclass装饰器

@dataclass的用途类似于Java中的Lombok,常用于数据模型类。

@dataclass可以帮我们省略掉__init__()__eq__()__repr__()等常见方法的手写代码,专注于业务逻辑,@dataclass中,有变量名且加了类型的变量,无论是否有默认值都会自动转换为实例变量,自动添加到__init__()中,有变量名不加类型但是有默认值的变量,默认生成为类变量,有变量名不加类型又无默认值的变量写法会直接报错。

对于有变量名且加了类型的变量,无论是否有默认值的情况,@dataclass既能生成带默认值的实例变量,也能生成不带默认值的实例变量,无默认值的实例变量必须在前,带默认值的实例变量必须在后,否则语法报错。类变量放哪都行,无顺序限制

例:

from dataclasses import dataclass
from datetime import date, datetime

@dataclass
class Book:

    # 会生成类变量:所有书籍实例对象共享
    # 类变量,顺序无限制
    publisher = "机械工业出版社"

    #会生成实例变量,无默认值
    bookName: str
    price: float
    author: str
    course_date: date
    start_time: datetime
    
    # 会生成实例变量,有默认值
    num: int = 12
    id: int = None

⚠️ 需要注意的是,可变默认值不要直接用(如 [], {}),而是该用None,因为默认值是在类定义时创建一次,然后被所有实例共享,现在的高版本python解释器会直接报错禁止这种写法。

⚠️@dataclass装饰器会自动生成__init__(),一旦手写了__init__()会导致装饰器失效,所有自动功能全部关闭,如果想用了装饰器又要加自定义的初始化逻辑,用__post_init__(self, ...)避免冲突

例:

from dataclasses import dataclass
from datetime import date, datetime

@dataclass
class Book:

    # 类变量:所有书籍实例对象共享
    publisher = "机械工业出版社"

    id: int = None
    bookName: str = None
    price: float = None
    author: str = None
    course_date: date  = None
    start_time: datetime = None

    # 初始化后校验价格合法性
    def __post_init__(self):
        print('__post_init__')
        if self.price is not None and self.price < 0:
            raise ValueError("书籍价格不能为负数!")


if __name__ == '__main__':
    print(Book.publisher)
    book = Book(id = 1, bookName='疯狂python讲义', price=2.3)
    print(book)
    book = Book(id = 1, bookName='疯狂python讲义', price=-2.3)
机械工业出版社
__post_init__
Book(id=1, bookName='疯狂python讲义', price=2.3, author=None, course_date=None, start_time=None)
__post_init__
Traceback (most recent call last):
  File "D:\PycharmProjects\python-lang-test\clazz\t3.py", line 27, in <module>
    book = Book(id = 1, bookName='疯狂python讲义', price=-2.3)
           ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 9, in __init__
  File "D:\PycharmProjects\python-lang-test\clazz\t3.py", line 21, in __post_init__
    raise ValueError("书籍价格不能为负数!")
ValueError: 书籍价格不能为负数!

__eq__()的重写,也会导致比较对象时,比较的是成员变量的值,而不是地址

if __name__ == '__main__':

    book1 = Book(id = 1, bookName='疯狂python讲义', price=2.3)
    book2 = Book(id = 1, bookName='疯狂python讲义', price=2.3)
    print(book1 == book2) # True

3.继承

继承的类,在类后面的小括号中声明,子类初始化,会调用父类构造方法初始化从父类继承来的属性

class Person:

    def __init__(self, name, age):
        print('Person init')
        self.name = name
        self.age = age

    def speak(self):
        print(self.__dict__)

class Student(Person):
    pass

if __name__ == '__main__':
    stu = Student(name='xiaohui', age=19)
    stu.speak()
    print(type(stu))
Person init
{'name': 'xiaohui', 'age': 19}
<class '__main__.Student'>

如果自己实现了和父类相同定义的方法,则以子类的为准

class Person:

    def __init__(self, name, age):
        print('Person init')
        self.name = name
        self.age = age

    def speak(self):
        print(self.__dict__)

class Student(Person):

    def speak(self):
        print('Student', self.__dict__)

if __name__ == '__main__':
    stu = Student(name='xiaohui', age=19)
    stu.speak()
    print(type(stu))
Person init
Student {'name': 'xiaohui', 'age': 19}
<class '__main__.Student'>

对于属性,可以使用从父类继承来的属性,也能自定义子类自己的属性,在创建对象时,需要super().__init__()初始化父类属性

class Person:

    def __init__(self, name, age):
        print('Person init')
        self.name = name
        self.age = age

    def speak(self):
        print(self.__dict__)

class Student(Person):

    def __init__(self, name, age, grade, clazz, stu_no):
        super().__init__(name, age)
        # Person.__init__(self, name, age) 等效写法
        self.grade = grade
        self.clazz = clazz
        self.stu_no = stu_no


    def speak(self):
        print('Student', self.__dict__)

if __name__ == '__main__':
    stu = Student(
        name='xiaohui',
        age=19,
        grade='3',
        clazz='6',
        stu_no='0735'
    )

    stu.speak()
    print(type(stu))
Person init
Student {'name': 'xiaohui', 'age': 19, 'grade': '3', 'clazz': '6', 'stu_no': '0735'}
<class '__main__.Student'>

4.对象初始化

python对象初始化o = Obj(),会经历以下几个过程:

  1. 调用该Obj类的__new__(cls,...)方法,在内存中开辟空间,创建一个空的实例对象
  2. __new__(cls,...)执行完成,必须返回一个当前Obj类的实例对象,就是后续的self
  3. python自动把返回的对象,传给__init__(self,...)的第一个参数self
  4. 调用__init__(self,...)给这个空对象绑定属性,初始化数据
  5. 返回初始化完成的实例对象给变量o

其中:

  • __new__(cls,...)是类方法,但是无需显式添加@classmethod装饰器
  • __new__(cls,...)必须有返回值,返回当前类对象时执行__init__(self,...),返回None则跳过__init__(self,...)
  • __new__(cls,...)的时机一定早于__init__(self,...),先创建再初始化
  • 永远不要出现多个生效的__init__(self,...)

例:


class Person:
    def __new__(cls, name):
        obj = super().__new__(cls) #调用父类(直到object)的__new__方法开内存空间,
        print('new')
        return obj

    def __init__(self, name):
        print('init')
        self.name = name


if __name__ == '__main__':
    p = Person('liming')
    print(p.name)

5.重写

之前说过,子类可以定义与父类相同定义的方法,就是重写,父类方法仍然能通过super()调用到

class Person:

    def __init__(self, name, age):
        print('Person init')
        self.name = name
        self.age = age

    def speak(self):
        print('Person', self.name, self.age)

class Student(Person):

    def __init__(self, name, age, grade, clazz, stu_no):
        super().__init__(name, age)
        # Person.__init__(self, name, age) 等效写法
        self.grade = grade
        self.clazz = clazz
        self.stu_no = stu_no


    def speak(self):
        super().speak()
        print('Student', self.grade, self.clazz, self.stu_no)

if __name__ == '__main__':
    stu = Student(
        name='xiaohui',
        age=19,
        grade='3',
        clazz='6',
        stu_no='0735'
    )

    stu.speak()
    print(type(stu))
Person init
Person xiaohui 19
Student 3 6 0735
<class '__main__.Student'>

6.多重继承

Python支持多继承,父类写两个代表同时继承两个类,但是构造函数必须分别使用各个父类的构造函数,不能直接用super(),否则会直接用第一个父类的构造函数

类的__mro__属性,可以显示类查找属性方法的顺序,但是不包含实例自身的属性和方法

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age


class Student:
    def __init__(self, grade, clazz, stu_no):
        self.grade = grade
        self.clazz = clazz
        self.stu_no = stu_no

class Freshman(Person, Student):
    def __init__(self, name, age, grade, clazz, stu_no, major):
        Person.__init__(self, name, age)
        Student.__init__(self, grade, clazz, stu_no)
        self.major = major



if __name__ == '__main__':
    fm = Freshman(
        name='xiaohui',
        age=19,
        grade='3',
        clazz='6',
        stu_no='0735',
        major='cs'
    )

    print(fm.__dict__)
    print(Freshman.__mro__)
{'name': 'xiaohui', 'age': 19, 'grade': '3', 'clazz': '6', 'stu_no': '0735', 'major': 'cs'}
(<class '__main__.Freshman'>, <class '__main__.Person'>, <class '__main__.Student'>, <class 'object'>)

7.抽象类

抽象类是一种不能直接实例化的类,通常作为规范让子类去继承,子类继承后,必须实现它定义的抽象方法

抽象类要继承ABC,抽象方法要@abstractmethod修饰且不应有具体实现

抽象类中,不是只能有抽象方法,也能有自行实现的方法

抽象类中不含抽象方法时,直接实例化不会导致语法出错,但是不推荐

from abc import ABC, abstractmethod

class Run(ABC):
    
    @abstractmethod
    def run(self):
        pass

声明一个子类继承自抽象类,需要实现抽象方法

from abc import ABC, abstractmethod

class Run(ABC):

    @abstractmethod
    def run(self):
        print('run')

class Person(Run):
    def run(self):
        print('person run')

if __name__ == '__main__':
    p = Person()
    p.run()

8.多态

同一方法名,在使用不同对象调用时,呈现不同的行为,就是多态。

python中的多态,分为标准多态和鸭子多态。

先看标准多态,python里面的标准多态和其他语言基本相同,都需要满足三个条件

  1. 继承关系
  2. 父类引用指向了子类对象
  3. 方法重写
class Animal:
    def speak(self):
        print('动物会叫')

class Cat(Animal):
    def speak(self):
        print('喵喵')

class Dog(Animal):
    def speak(self):
        print('旺旺')

def fun(animal: Animal):
    animal.speak()

if __name__ == '__main__':
    animal:Animal = Cat()
    fun(animal) #喵喵

    animal: Animal = Dog()
    fun(animal) #旺旺

鸭子多态,意思是说:如果一个东西看起来和叫起来都像鸭子,那它就是鸭子🦆

鸭子类型(duck typing)是一种编程风格,不检查对象的类型,只关注对象能否做某件事

class Animal:
    def speak(self):
        print('动物会叫')

class Pig: #不继承
    def speak(self):
        print('噜噜')

def fun(animal: Animal):
    animal.speak()

if __name__ == '__main__':
    animal:Animal = Pig()
    fun(animal) #噜噜

9.内存分析


"如果文章对您有帮助,可以请作者喝杯咖啡吗?"

微信二维码

微信支付

支付宝二维码

支付宝


Python中的类和对象
https://blog.liuzijian.com/post/2026/01/20/python-class-and-object/
作者
Liu Zijian
发布于
2026年1月20日
更新于
2026年8月23日
许可协议