主题
Python面向对象
构造方法
Python 类编程见到最多的构造方法是 __init__
,但实际上,真正创建实例对象的是 __new__
方法。面试的时候经常会问:__init__
和 __new__
哪个先被调用?肯定是先创建实例了才能再赋值。
__new__
是顶层类 object 的静态方法(因为是特例所以不需要显示声明),它的第一个参数就是实例所属的类,其余参数就是传入的初始化参数。__new__
必须要返回实例对象,才能将返回的实例作为 __init__
的第一个参数继续调用 __init__
。
__init__
是一个实例方法,控制实例的初始化过程,把初始值赋给实例属性,做一些额外的操作等。
python
class Person:
def __new__(cls, *args, **kwargs):
print("__new__", args, kwargs)
return super().__new__(cls)
# self 就是 __new__ 所返回的实例对象
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Tom", age=10)
# __new__ ('Tom',) {'age': 10}
__new__
的主要目的是为不可变类型的字类(如int、str或tuple)定制实例创建过程,以及用于元类中被重载以便定制类创建过程。
例子:实现一个永远都是正数的整数类型。
python
class PositiveInteger(int):
def __new__(cls, value):
return super().__new__(cls, abs(value))
i = PositiveInteger(-5)
print(i) # 5
类方法、实例方法和静态方法
python
class Date:
name = "China" # 类属性
def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year
# 类方法,cls 表示类本身。
@classmethod
def from_string(cls, date_as_string):
"""创建一个实例"""
year, month, day, = map(int, date_as_string.split('-'))
date = cls(day, month, year)
return date
# 实例方法,self 表示实例对象
def show(self):
print(self.year, self.month, self.day, self.name)
# 静态方法
@staticmethod
def is_date_valid(date_as_string):
year, month, day = map(int, date_as_string.split('-'))
return day <= 31 and month <= 12 and year <= 3999
d = Date(2025, 7, 16)
# d.from_string(val) = Date.from_string(val) = from_string(Date, val)
d.from_string("2025-07-15")
d.show() # d.show() = Date.show(d)
d.is_date_valid("2025-07-15") # Date.is_date_valid(val)
Date.is_date_valid("2025-35-15")
类的方法 | 类能否调用 | 实例能否调用 | 是否可访问类属性 | 是否可访问实例属性 |
---|---|---|---|---|
类方法 | ✅ | ✅ | ✅ | ❌ |
实例方法 | ❌ | ✅ | ✅ | ✅ |
静态方法 | ✅ | ✅ | ❌ | ❌ |
提示
类方法的 cls
和实例方法的 self
并不是固定的,可以是任何非关键字变量名称(比如换成 this
完全没问题)。只是约定这样方便理解。
封装
Python 不像 Java 和 C++ 那样有专门的关键字来声明私有属性或保护属性,Python 采用下划线+名称
的方式封装:
python
class Person:
def __init__(self, name, age):
self.name = name # 公共属性
self._love = "" # 受保护属性
self.__age = age # 私有属性
self.def_ = None # 与关键字冲突的名称通常在后加下划线
单下划线表示受保护属性 protected
,双下划线表示私有属性 private
,其它表示公共属性 public
。这种规则属性和方法都适用。
- 私有属性(或方法,下同)只能在类本身中访问,实例对象和子类都无法访问。
- 受保护属性和公共属性在使用时没啥区别,其本质上是一种约定,告知开发人员这是一个受保护属性,应该只能在类本身和子类中访问,而在外部不应直接操作。实际上,单下划线的方式多用于解决命名冲突。
此外,私有属性真的无法访问吗?当然不是,只是因为 Python 对双下划线的属性作了特殊处理:
python
p = Person("Tom", 10)
print(p.__dict__)
# {'name': 'Tom', '_love': '', '_Person__age': 10, 'def_': None}
p._Person__age = 18 # 可以访问并修改
可以看到,双下划线的 __age
属性已经变成了 _Person__age
,即 _ + 类名 + 私有属性名称
。
继承
Python 不像 C++ 那样有私有继承、保护继承等,所以 Python 的继承比较简单。
super方法
super 多用来调用父类方法。假设有如下继承关系:
python
class A:
def go(self):
print("A go!")
def stop(self):
print("A stop!")
class B(A):
def go(self):
super().go() # 等同于 super(B, self).go()
print("B go!")
def stop(self):
super().stop()
print("B stop!")
class C(A):
def go(self):
super(C, self).go()
print("C go!")
def stop(self):
super(C, self).stop()
print("C stop!")
class D(B, C):
def go(self):
super().go()
print("D go!")
def stop(self):
super(B, self).stop()
# super(C, self).stop()
print("D stop!")
print(D.mro())
d = D()
d.go()
print("------")
d.stop()
输出为:
console
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
A go!
C go!
B go!
D go!
------
A stop!
C stop!
D stop!
MRO列出了类的继承顺序:D -> B -> C -> A -> object。
super 方法的原型大概是这样:
python
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
所以类 D 中的 super(B, self).stop()
表示在MRO列表中找到B的下一个类,即类 C ,而调用类 C 的 stop
方法时又会去调用类 A 的方法,因此输出的顺序是 A -> C -> D。
想一想,如果把类 D 的 super(B, self).stop()
换成 super(C, self).stop()
会输出什么?
多态
python
class Animal:
def speak(self):
raise NotImplementedError("子类必须实现此方法")
class Dog(Animal):
def speak(self):
return "汪汪!"
class Cat(Animal):
def speak(self):
return "喵喵!"
def animal_speak(animal):
print(animal.speak())
dog = Dog()
cat = Cat()
animal_speak(dog) # 输出: 汪汪!
animal_speak(cat) # 输出: 喵喵!
python
class Dog:
def speak(self):
return "汪汪!"
class Cat:
def speak(self):
return "喵喵!"
class Robot:
def speak(self):
return "我是机器人!"
def make_sound(obj):
print(obj.speak())
# 多态体现
make_sound(Dog()) # 输出: 汪汪!
make_sound(Cat()) # 输出: 喵喵!
make_sound(Robot()) # 输出: 我是机器人!
混入类
场景:往往通过多继承来和其他映射对象混入使用,扩展类的功能。
注意:
- 混入类不能直接被实例化使用。
- 混入类没有状态信息,没有实例属性,即没有
__init__
方法。一般明确使用__slot__ = ()
。
Django 中就使用了很多混入类。