本文围绕 Python 中函数重载、函数多态和类继承三条线索,梳理原文中的代码实现与关键结论。原文的核心观点是:Python 不支持自定义函数重载,同名函数后定义会覆盖前定义;多态可通过参数类型分支和可变参数实现;继承中要区分类属性与实例属性,并理解 super() 在方法扩展中的调用方式。
一、函数重载:同名覆盖与可变参数方案
原文把重载理解为同一函数名处理不同数量或类型参数。常见实现思路有三种:
1. 实现同名不同参数函数:需要为不同参数分别写实现,代码较繁琐,可读性不强。
2. 根据实参类型确定调用函数:通过类型检查选择不同函数,代码更清晰,可读性更高。
3. 通过 *args 和 **kwargs 形参重载:args 接收可变位置参数,kwargs 接收可变关键字参数,可同时支持不定数量参数和不同类型参数。
原文给出的同名 multiply 示例:
- # First multiply method.
- # Takes two argument and print their value
- def multiply(a, b):
- re = a * b
- print(re)
- # Second multiply method.
- # Takes three argument and print their value
- def multiply(a, b, c):
- re = a * b * c
- print(re)
- multiply(3, 4)
- multiply(3, 4, 5) # This line will call the second multiply method.
复制代码
这里要结合原文结论理解:Python 不支持自定义函数重载。用相同名称定义多个函数时,后一个函数会覆盖前一个函数,命名空间中每个函数名始终只有一个 entry。因此,按覆盖后的规则,multiply 实际只保留三参数版本;multiply(3, 4) 不能按重载匹配到第一个定义,而 multiply(3, 4, 5) 才会调用后定义的三参数实现。原文也正因此指出,重载同名函数并不是 Python 自定义函数的原生能力。
二、函数多态:用参数控制同一函数的工作方式
原文用 add 函数演示多态:传入不同 datatype,让同一个函数做整数加法或字符串拼接。
- # Function to take multiple arguments
- def add(datatype, *args):
- # if datatype is int
- # initialize answer as 0
- if datatype == 'int':
- answer = 0
- # if datatype is str
- # initialize answer as ''
- if datatype == 'str':
- answer = ''
- # Traverse through the arguments
- for x in args:
- # This will do addition if the arguments are int.
- # Or concatenation if the arguments are str
- answer = answer + x
- print(answer)
- # Integer
- add('int', 5, 6) # 11
- # String
- add('str', 'Hi ', 'Bob') # Hi Bob
复制代码
关键逻辑是:datatype 为 int 时 answer 初始化为 0;datatype 为 str 时 answer 初始化为空字符串;随后循环遍历 args,用 + 完成累加或拼接。于是 add('int', 5, 6) 输出 11,add('str', 'Hi ', 'Bob') 输出 Hi Bob。
三、函数继承:类属性、实例属性与 super 调用
Python 中类支持继承,也支持多继承。默认情况下继承自 object,object 是所有类的基类。一个类可以继承另一个类,继承者叫子类,被继承者叫父类。继承让子类直接拥有父类中的内容,属性和方法都可以被继承。原文特别提醒:__slots__ 对应的值不会被继承。
基础继承示例:
- class Person(object):
- num = 61
- # 注意:__slots__对应的值不会被继承
- __slots__ = ('name', 'age', 'sex')
- def __init__(self):
- self.name = '张三'
- self.age = 0
- self.sex = '男'
- def show_message(self):
- print('%s你好吗?' % self.name)
- # Student类继承自Person类
- class Student(Person):
- pass
- # 创建学生对象
- stu1 = Student()
- # 对象属性可以继承
- print(stu1.name, stu1.age, stu1.sex)
- # 类的字段可以继承
- print(Student.num)
- # 对象方法可以继承
- stu1.show_message()
- p1 = Person()
- # p1.color = '黄色'
- stu1.color = '白色'
- print(stu1.color)
复制代码
原文备注指出,__init__ 中的 self.name、self.age、self.sex 是实例属性,因为 self 代表实例;直接定义在类下面的是类属性。区分类属性和实例属性,是理解后续子类添加属性、调用父类方法的基础。
1. 子类添加方法
子类可以直接声明新方法。添加后,子类能调用自己的方法,也能调用父类方法,但父类不能调用子类的方法。
重写父类方法分两种:完全重写,直接在子类中重新实现父类方法,覆盖父类功能;部分重写,在子类方法中通过 super() 调用父类方法,再添加新功能。
原文给出的注意点包括:
a. 子类方法中可以通过 super() 调用父类方法;super(类, 对象) 获取对象中父类的部分,要求对象是这个指定类的对象。
b. 静态方法中不能使用 super()。
方法调用的查找过程是:通过对象或类调用方法时,先看当前类中是否声明过该方法;如果声明过就直接调用当前类对应的方法;如果当前类没有声明过,就找父类中有没有声明;父类也没有,就继续找父类的父类,直到 object;如果 object 中也没有声明过,程序才会崩溃。
构造函数与继承中的方法示例:
- class Person:
- # 类的字段
- num = 61
- # 对象属性
- def __init__(self): # 有self则为实例方法,代表实例
- self.name = '张三' # 实例变量,保存特征值
- self.age = 0
- self.sex = '男'
- def fun1(self): # 有self则为实例方法,代表实例
- print('Person的对象方法')
- # 方法
- def show_message(self): # 有self则为实例方法,代表实例
- print('%s,你好吗?' % self.name)
- @staticmethod
- def info():
- print('我是人类')
- class Student(Person):
- def study(self):
- print('%s在学生' % self.name)
- '''
- 类变量、实例变量、实例方法、类方法、静态方法类方法和静态方法不能访问实例变量
- 在函数中不带self的是类方法或静态方法,不能访问实例变量
- 实例方法中默认参数self,类方法中默认参数cls,静态方法没有默认参数
- 类方法中用@classmethod 这种装饰器,静态方法中用@staticmethod 这种装饰器
- '''
- @classmethod # 类方法。装饰器
- def message(cls):
- super().info()
- print('我是学生!')
- # 完全重写
- @staticmethod # 静态方法。装饰器
- def info():
- print('我是学生!!!')
- # 保留父类的功能
- def show_message(self):
- super().show_message()
- print('我去上学~')
- super().fun1()
- # Student.info()
复制代码
原文进一步解释了实例方法、类方法和静态方法的差异:类中的方法默认都是普通方法,第一个参数是 self,这种方法只能被实例调用;如果被 @classmethod 装饰,这个方法就是类方法,可以被类和实例调用。示例:
- class Student():
- @classmethod # 类方法。装饰器
- def message(self):
- print('我是学生!')
- stu = Student()
- print(stu.message()) # 输出我是学生
- print(Student.message()) # 输出我是学生
- class Student():
- def message(self):
- print('我是学生!')
- stu = Student()
- print(stu.message()) # 输出我是学生
- print(Student.message()) # 报错
复制代码
报错信息为:
- File 'D:/xxxxx/xxxx.py', line 199, in <module> print(Student.message())
- TypeError: message() missing 1 required positional argument: 'self'
复制代码
因为 message() 第一个参数是 self,这种方法只能被实例调用。带参函数 fun1(self) 也一样:实例方法需要实例对象来访问。原文示例中,p = Person() 后 p.fun1() 和 Person().fun1() 都输出 Person的对象方法,而 Person.fun1() 会报错:
- File 'D:/xxxxx/xxxx.py', line 76, in <module> print(Person.fun1())
- TypeError: fun1() missing 1 required positional argument: 'self'
复制代码
不带参数、没有 self 的 info() 则可以直接调用。原文说明:在函数中不带 self 的是类方法或静态方法,不能访问实例变量。因此 Person.info() 输出我是人类。
2. 子类添加属性
添加类的字段,可以直接在子类中添加新字段。添加对象属性时:a. 继承对象属性是通过继承父类的 __init__ 方法继承下来的;b. 如果想在保留父类继承下来的对象属性的前提下添加新的对象属性,需要在子类的 __init__ 方法中通过 super() 调用父类的 __init__ 方法。
- class Person:
- num = 61
- def __init__(self, name):
- self.name = name
- self.age = 0
- class Student(Person):
- number = 100
- def __init__(self, name):
- super().__init__(name)
- self.study_id = '001'
- print(Student.number, Student.num)
- stu1 = Student('小明')
- print(stu1.name, stu1.age, stu1.study_id)
复制代码
四、原文练习:动物类与猫类的继承初始化
练习要求:声明一个动物类,有属性:年龄、颜色、类型,要求创建动物对象时类型和颜色必须赋值,年龄可以赋值也可以不赋值;声明一个猫类,有属性:年龄、颜色、类型、爱好,要求创建猫对象时颜色必须赋值,年龄和爱好可以赋值也可以不赋值,类型不能赋值。
- class Aniaml:
- def __init__(self, type, color, age=0):
- self.type = type
- self.color = color
- self.age = age
- class Cat(Aniaml):
- def __init__(self, color, age=0, hobby=''):
- super().__init__('猫科', color, age)
- self.hobby = hobby
- an1 = Aniaml('犬科', '黄色')
- an2 = Aniaml('犬科', '黄色', 10)
- cat1 = Cat('白色')
- cat2 = Cat('灰色', 3)
- cat3 = Cat('灰色', hobby='睡觉')
- cat4 = Cat('灰色', 3, '睡觉')
复制代码
这段代码体现了子类通过 super().__init__ 复用父类初始化逻辑,同时把父类要求的类型固定为“猫科”,子类只暴露颜色、年龄、爱好三个参数,其中颜色必传,年龄和爱好使用默认值。
总结
原文围绕 Python 函数重载、多态和继承给出了实现代码与注意事项。需要记住的关键点包括:Python 不支持自定义函数重载,同名函数会覆盖;多态可以通过 datatype 分支配合 *args 实现;继承中要区分类属性和实例属性,子类可通过 super() 调用父类方法或父类 __init__;实例方法需要实例调用,类方法可被类和实例调用,静态方法中不能使用 super()。 |