Python面试题整理03——类-特性+方法
Python面试题整理03——类-特性+方法

Python面试题整理03——类-特性+方法

一些基础的定义方式、概念啥的不再赘述,我们直接进入稍微复杂一点的地方。

三特性

先从类的三个特性说起:封装、继承、多态。

封装

封装其实字面理解就很好理解,就是把一些东西封存起来,不让外部看到,有点黑匣子的意思。核心目的就是隐藏内部实现细节,只暴露接口。

属性定义一般来说有个公有和私有(双下划线,例如__self_name)的概念,不过python很特殊的一点是没有绝对的私有,实际上可以强制访问(_类名__变量名),这个也叫名称修饰。

class school:
    name = "喵喵大学"
    __self_name = "哈吉米大学"

a = school()
print(a.name)
print(a._school__self_name)
# 输出
# 喵喵大学
# 哈吉米大学

继承

继承也很好理解,就是代码复用,子类可以继承父类的属性和方法。

不过需要注意的是,如果重写了__init__,需要显式调用 super().__init__(),否则父类初始化逻辑不会执行,而是直接子类覆盖父类。如下代码所示。

class school:
    def __init__(self, num):
        self.name = "喵喵大学"
        self.num = num
    def get_name(self):
        print(self.name)
    def get_num(self):
        print(self.num)

a = school(100)
a.get_name()
a.get_num()

class school_new(school):
    def __init__(self, num, area):
        super().__init__(num)   # 调用父类构造,如果没有这句话,school_new定义的实例就不会有num和name属性,后续报错。
        self.area = area
    def get_area(self):
        print(self.area)

b = school_new(10,"bj")
b.get_name()
b.get_num()
b.get_area()

多态

唯一看起来复杂一点的概念。粗暴点理解,就是同样一个类函数方法,但是不同子类调用的结果会不同。其实就是简化了调用的操作。也就是把实现逻辑和调用区分开来。如下代码所示,假如获取学校名称有不同的来源:

class School:
    def get_name(self):
        raise NotImplementedError

import random
import time

class SchoolA(School):
    def get_name(self):
        time.sleep(0.5)           # 模拟计算
        return "喵喵大学(本地算法)"

class SchoolB(School):
    def get_name(self):
        time.sleep(1)             # 模拟网络
        return "汪汪大学(远程接口)"

class SchoolC(School):
    def get_name(self):
        time.sleep(0.2)           # 模拟数据库
        return "咕咕大学(数据库)"

多态可用的话,调用方完全不操心:

def print_school(school: School):
    print("学校信息:", school.get_name())

而如果不用多态,那么调用方就要复杂起来:

def print_school(school_type):
    if school_type == "A":
        time.sleep(0.5)
        print("喵喵大学(本地算法)")
    elif school_type == "B":
        time.sleep(1)
        print("汪汪大学(远程接口)")
    elif school_type == "C":
        time.sleep(0.2)
        print("咕咕大学(数据库)")

例子可能不太好,但是写代码多了总归会遇到,就是我改前面的类,然后后面调用的也要跟着改,代码量上来后就很容易出现牵一发而动全身的感觉,而多态就是解决此类的问题,甭管你父类、子类怎么折腾,我调用方式不用变。

属性

我们定义一个类常见的属性有类属性、实例属性,实例属性又有局部和全局啥的区别。本来写一下,感觉又没啥好写的,大概就是作用域、修改后影响一类的,可以自行写代码试试看一下就明白了。

类方法、实例方法、静态方法

类方法用于在没有实例时操作类本身,典型是工厂与构造替代;静态方法用于逻辑上属于类、但不依赖类或实例的工具逻辑;实例方法用于依赖对象状态的行为。

实例方法应该是最常见也是最常用的:

class User:
    def __init__(self, username):
        self.username = username

    # 这是一个实例方法
    def upgrade_username(self, new_name):
        self.username = new_name  # 它操作的是“这个”用户的名字
        print(f"名字已改为:{self.username}")

# 使用:
user_a = User("张三")
user_a.upgrade_username("张三三")  # 必须先有实例,才能改名

类方法:

class User:
    def __init__(self, username, age):
        self.username = username
        self.age = age

    @classmethod
    def from_json(cls, data):
        # 假设 data 是 {"name": "李四", "age": 20}
        # cls(name, age) 相当于调用 User(name, age)
        return cls(data['name'], data['age'])

# 使用:
json_data = {"name": "李四", "age": 20}
# 不需要先创建 user 对象,直接用类来调用
user_b = User.from_json(json_data)

其实初看有点脱裤子放屁的感觉,就是我已经有实例方法了,为什么还要搞个类方法?实际上二者应用范围有很大差异。类函数实际上是在说明,怎么样构造一个实例。而实例函数更多的是我已经有了一个实例,可以怎么用。上述我们类函数通过from_json构造了一个实例,如果后续我们有更多的数据类型,比如数据库、表单一类的,我们可以写User.from_db(…)、User.from_form(…),而不是局限于一个固定的实例创建方式。此外,从继承角度来看,类函数可以不用改代码,便捷的被继承。

静态方法:

class User:

    @staticmethod
    def check_age(age):
        return age >= 18

User.check_age(20)   # True
User.check_age(10)   # False

这个其实更好理解,就是一个不需要类、实例的方法,但是呢,从逻辑上来讲又和这个类有关系。当然也可以写出去,单独创建一个函数,个人觉得这个单纯看个人喜好,可能在中大型项目上归到一个类里比较好阅读理解。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注