当前位置: 首页 > news >正文

【日常学习】2025-8-22 类属性和实例属性+小白学调试

一、同名类属性和实例属性:普通类 

1. 类属性的作用:定义 “默认值” 和 “类型提示”

类中定义的menu_path: list = Nonelocator = None等类属性,主要有两个作用:

  • 类型提示:通过menu_path: list告诉开发者,这个属性预期是list类型(提高代码可读性和 IDE 提示能力);
  • 默认初始值:当实例未显式设置该属性时,默认值为None(明确属性的初始状态)。

2. 实例属性的作用:存储 “实例个性化值”

构造方法中self.menu_path = menu_path等赋值,是为了将每个实例特有的参数存储到实例中。即使与类属性同名,实例属性也会覆盖类属性(面向对象中,实例属性的优先级高于类属性)。

# 类属性默认值为 None
print(PageElement.menu_path)  # 输出: None# 创建实例时传递个性化值
elem = PageElement(menu_path=["首页", "股票"],locator=Locator(By.ID, "stock")
)# 实例属性覆盖了类属性
print(elem.menu_path)  # 输出: ["首页", "股票"](实例自己的值)

  • 类属性是 “模板级的默认配置”,实例属性是 “对象级的个性化配置”;
  • 当访问实例.属性时,Python 会先找实例属性,找不到才会用类属性(形成 “默认值 fallback” 机制)。

二、不同名类属性和实例属性:基类 

    之前的ElementBase中,类属性cache与实例属性p_cache不同名,是因为它们代表不同概念

    要彻底理解 ElementBase 中类属性与实例属性的 “概念差异”,需要结合 UI 自动化测试的业务场景 和 属性的实际功能—— 它们不是 “默认值与覆盖值” 的关系,而是 “全局规则” 与 “实例状态” 的分层设计,各自负责不同的功能维度。

    ElementBase 是 UI 自动化框架中 “页面元素” 的基类(比如按钮、输入框、列表等元素都继承它)。类属性和实例属性的设计,对应了自动化测试中两个核心需求:

    • 类属性:定义 “所有同类元素的通用规则 / 模板”(比如 “默认是否允许缓存”“基础定位器是什么”);
    • 实例属性:记录 “单个元素实例的运行时状态”(比如 “实际找到的元素对象”“该实例是否真的启用了缓存”)。

     比如ElementBase 中:

    • 类属性负责 “规则定义”(是否允许缓存、如何定位);
    • 实例属性负责 “执行状态”(是否实际缓存、找到的元素对象)。

    它们是 不同维度的概念,就像 “交通规则(限速 120)” 和 “汽车实际速度(100km/h)”—— 规则和实际状态是两回事,不能说 “汽车速度是规则的默认值”。

    1. cache(类属性) vs self.p_cache(实例属性):普通类 

    • 类属性 cache = False

    • 这是 “框架级的默认规则”,表示 “所有继承 ElementBase 的元素类,默认不允许缓存元素”(缓存指:找到元素后保存起来,避免每次操作都重新查找,可能影响动态页面的准确性)。
      它是给 “类” 设定的规则,比如:

      class Button(ElementBase):cache = True  # 按钮类单独修改规则:允许缓存(覆盖类属性的默认值)
      

      这里的 cache 控制的是 “某类元素是否允许缓存”(规则层面)。

    • 实例属性 self.p_cache = False

    • 这是 “实例级的实际状态”,表示 “当前这个元素实例是否真的启用了缓存”(执行层面)。
      它可以根据实例的具体场景,覆盖类的规则,比如:

      btn = Button()
      btn.p_cache = False  # 虽然按钮类允许缓存,但这个按钮实例临时关闭缓存
      

      这里的 p_cache 控制的是 “这个实例是否实际使用缓存”(状态层面)。

    核心差异cache 是 “是否允许”(规则),p_cache 是 “是否实际使用”(状态),两者是 “规则 vs 执行” 的不同维度,而非 “默认 vs 覆盖”。

    2. locator(类属性) vs self.fullLocator(实例属性):基类 

    • 类属性 locator = None
      这是 “元素定位的基础模板”子类需要重写它来定义 “该类元素的基础定位方式”。比如:

      class SearchInput(ElementBase):locator = Locator(By.ID, "search-input")  # 搜索框的基础定位器
      

      它是 “某类元素应该如何查找” 的通用规则(比如所有搜索框都通过 id="search-input" 定位)。

    • 实例属性 self.fullLocator = None
      这是 “实例运行时的完整定位路径”,可能结合了父元素的定位器动态生成。比如:
      父元素是表单(locator = By.ID, "form"),子元素是输入框(基础 locator = By.NAME, "username"),则 fullLocator 可能是 By.ID, "form" → By.NAME, "username"(完整路径)。

    核心差异locator 是 “基础定位模板”(静态规则),fullLocator 是 “动态拼接的实际定位路径”(运行时结果),两者是 “模板 vs 结果” 的不同概念。

    3. page_container_loc/page_title_loc(类属性) vs  self.element(实例属性)

    • 类属性 page_container_loc 等
      这是 “页面级通用元素的定位器”(比如所有股票页面都有 stock-view-page-container 容器),是框架预定义的 “公共定位规则”,供所有实例共享使用(比如任何元素都可以通过它找到页面容器)。

    • 实例属性 self.element = None
      这是 “实例实际找到的 Web 元素对象”(比如 Selenium 的 WebElement 或 Appium 的 MobileElement),是执行 find_element 后的实际结果,每个实例的 element 指向自己对应的页面元素(比如 “登录按钮实例” 的 element 指向页面上的登录按钮,“注册按钮实例” 的 element 指向注册按钮)。

    核心差异page_container_loc 是 “找元素的规则”,self.element 是 “找到的元素本身”,两者是 “方法 vs 结果” 的不同概念。

    • 若类属性和实例属性代表同一概念(只是默认值 vs 个性化值)
    • 若代表不同概念(如 “全局配置” vs “实例状态”)

    三、子类的类属性重写 

    类属性可以被子类重写,这是面向对象编程中继承特性的重要体现。子类重写父类属性后,会用自己的属性值覆盖父类的默认值,让子类拥有更贴合自身需求的配置。

    假设ElementBase是父类,我们创建一个子类ButtonElement(按钮元素),重写它的locatorcache属性:

    # 父类:ElementBase
    class ElementBase(metaclass=ElementMeta):locator = None  # 父类默认定位器为Nonecache = False   # 父类默认不缓存# 其他属性和方法...# 子类:ButtonElement(继承自ElementBase)
    class ButtonElement(ElementBase):# 重写父类的locator:按钮有自己的定位规则locator = Locator(By.XPATH, ".//button")# 重写父类的cache:按钮允许缓存cache = True
    

    原来如此,基类的locator就是一个规则,=None是在预留,搭个框架初始化而已。

    子类可以自己重写:是否需要定位功能,缓存功能?具体定位规则是什么,具体怎么缓存的。

    属性是小写开头locator,重写调用的就是diver包下面的同名首字母大写函数制定定位规则。

    所以想要理解这个代码为什么这么写,一定需要结合具体的框架内容甚至业务逻辑,内容。

    • 父类ElementBase定义通用默认规则(比如cache = False);
    • 子类(按钮、输入框、下拉框等)根据自身特性重写规则(比如按钮可以缓存cache = True,动态生成的输入框不缓存cache = False)。

    这样既保证了所有元素有统一的基础配置,又允许子类灵活定制,符合 “通用父类 + 个性化子类” 的设计思想。这是子类重写基类同名类属性!

    子类重写的是类属性(属于子类本身),而之前说的实例属性(如self.p_cache)是每个实例自己的状态

    # 父类:ElementBase
    class ElementBase(metaclass=ElementMeta):locator = None  # 父类默认定位器为Nonecache = False   # 父类默认不缓存# 其他属性和方法...# 子类:ButtonElement(继承自ElementBase)
    # 子类重写类属性
    class ButtonElement(ElementBase):# 重写父类的locator:按钮有自己的定位规则locator = Locator(By.XPATH, ".//button")# 重写父类的cache:按钮允许缓存cache = True  # 类属性:所有按钮默认允许缓存# 实例修改自己的状态
    btn1 = ButtonElement()
    btn1.p_cache = False  # 实例属性:这个按钮实例不实际使用缓存
    

    • 子类重写的cache是 “该类元素的默认规则”; 说明这个子类的规则是允许缓存的。(类属性代表着一种大多数都遵循这个规则的含义)
    • 实例的p_cache是 “这个实例的实际执行状态”。子类是允许缓存的,但那是给实例权力,但实例完全可以不执行啊,这个按钮实例它就不使用缓存,就是说可以缓存,但是我这个实例不需要这个功能。这是子类不同名的实例属性!

    两者配合,既实现了 “类级别的规则定制”,又支持 “实例级别的灵活调整”,非常适合复杂框架的设计。

    总结

    子类完全可以重写父类的属性,这是面向对象中 “继承 + 定制” 的核心用法。在你的框架中,这种机制让不同类型的元素(按钮、输入框等)能在共享父类基础功能的同时,拥有自己的专属配置,既减少重复代码,又保证了灵活性。

    四、调试有学问!

    调试是理解代码流程最直接有效的方法,尤其是对于复杂框架。作为 “调试新手”,掌握一些关键细节和技巧能让你效率翻倍。

    【1】调试前的 3 个准备工作(关键!)

    • 明确目标:先想清楚 “我要搞懂什么”

      • 比如:“这个show_case_step()装饰器是怎么给方法加日志的?”“ElementBase__init__执行时,parent属性是从哪来的?”
      • 带着具体问题调试,避免漫无目的地单步执行。
    • 找到入口点:从 “触发函数的第一行代码” 开始

      • 比如框架中某个测试用例的test_xxx()方法,或者你知道的某个公开接口(如driver.find_element()),从这里打第一个断点。
    • 简化场景:用最小化的代码复现流程

      • 比如在框架里写一个最简单的测试用例(只调用你要研究的函数),减少无关代码干扰(比如注释掉其他测试步骤)。简单的脚本写想要调试的函数,或者注释掉别的行。

    【2】调试核心操作:断点 + 变量查看(以 PyCharm 为例,其他 IDE 逻辑类似)

    1. 断点怎么打?3 种常用断点类型
    • 普通断点:点击代码行号右侧的空白处(会出现红色圆点),程序执行到这里会暂停。
      ✅ 适合:函数入口(如def __init__的第一行)、关键逻辑分支(if/else)。

    • 条件断点:右键断点→设置条件(如menu_path == ["首页"]),只有满足条件时才暂停。
      ✅ 适合:循环中只想看特定情况(比如第 5 次循环)、多分支中只关注某条路径。

    • 异常断点:PyCharm 顶部RunView Breakpoints→点击+→选Python Exception Breakpoint,输入异常类型(如AttributeError),程序抛出该异常时会自动暂停。
      ✅ 适合:定位 “不知道在哪报错” 的问题(比如框架中偶尔出现的NoneType错误)。

    2. 暂停后该看什么?4 个核心信息

    程序暂停后,重点关注这几个窗口(PyCharm 底部):

    窗口 / 操作作用新手必看内容
    Variables显示当前作用域的所有变量(局部变量、全局变量、实例属性等)实例的self.xxx属性(如self.element的值)、函数参数值
    Call Stack显示函数调用链(谁调用了当前函数,当前函数又调用了谁)从上到下是调用顺序,点击某一行可跳转到对应函数代码
    Watches手动添加变量 / 表达式(如self.parent.locator),实时查看其值跟踪复杂属性(比如多层嵌套的locator
    Console调试时的交互式命令行,可直接输入代码执行(如print(self.menu_path)临时验证猜想(比如 “这个变量是不是None”)

    3. 单步执行:4 个按钮怎么用?

    暂停后,用这 4 个按钮控制执行节奏(PyCharm 调试工具栏):

    按钮(图标)功能什么时候用?
    单步执行(Step Over, F8)执行当前行,不进入当前行调用的函数内部只想看当前函数的流程,跳过子函数
    单步进入(Step Into, F7)执行当前行,进入当前行调用的函数内部(如果是自定义函数)想钻进子函数看细节(比如show_case_step()装饰器的逻辑)
    单步跳出(Step Out, Shift+F8)从当前函数内部跳出,回到调用它的地方子函数看得差不多了,想回到上层函数
    继续执行(Resume Program, F9)从当前断点继续执行,直到遇到下一个断点想直接跳到下一个断点(比如跳过循环中间步骤)

    【3】框架代码调试的实战技巧

    结合你提到的ElementBase和元类代码,举几个具体场景:

    1. 想知道 “元类ElementMeta如何修改类属性”
    • 断点位置ElementMeta__new__方法第一行(def __new__(cls, name, base, attr):)。
    • 看什么
      • name:当前正在创建的类名(比如ElementBase或它的子类);
      • attr:类的属性字典(里面有locatorcache等,调试时能看到元类如何给attr添加elementparent);
      • 执行到attr[attr_name] = show_case_step()(attr_value)时,用Step Into钻进show_case_step()装饰器,看它如何包装方法。

    2. 想跟踪 “self.element是何时被赋值的”
    • 断点位置ElementBase__init__方法(self.element = None这行),以及所有可能修改self.element的地方(比如搜索self.element =在框架中的所有出现位置)。
    • 操作
      • 先在__init__断点确认初始值是None
      • Resume Program跳到下一个修改self.element的断点,看是哪个函数(比如find_element())给它赋了值;
      • Variables窗口观察self.element的类型(比如是不是 Selenium 的WebElement)。

    3. 看不懂 “函数调用链”(比如 “测试用例→页面元素→元类” 的调用顺序)
    • Call Stack窗口:

      • 比如执行测试用例时暂停,Call Stack顶部是当前函数(比如ElementBase.__init__),下面一行是调用它的函数(比如Button.__init__),再下面是更上层的调用(比如test_case());
      • 点击Call Stack中的任意一行,能直接跳转到对应的调用位置,轻松理清 “谁调用了谁”。

    【4】调试习惯:边调边记

    调试时拿个笔记(或记事本)记录:

    • 关键函数的调用顺序(比如元类__new__ElementBase.__init__子类方法);
    • 变量的变化节点(比如self.elementfind_element后从None变成了具体对象);
    • 自己的猜想和验证结果(比如 “猜p_cachecache的实例化,调试后发现确实在__init__里用了self.p_cache = ...”)。

    记下来的内容就是你理解框架的 “线索”,比单纯调试更有收获。

    这一点也同样说明了学习或者刷题不要一上来就看答案,这种不思考直接接受的方式会让我们忽略很多思考的关键点和细节,细节决定成败,懂了细节才能理解到脑子里去。

    五、类的初始化和实例初始化 

    【1】类初始化的触发时机

    首先要明确:类不是一开始就加载到内存的,而是在程序第一次 “需要用到这个类” 时才会触发初始化。比如:

    • 当你写obj = ElementBase()(创建类的实例);
    • 当你访问类的静态属性 / 方法(比如ElementBase.page_container_loc);
    • 当子类初始化时(因为子类依赖父类,会先加载父类)。

    【2】类初始化时具体加载什么?

    以你的ElementBase类为例,初始化过程会依次处理这些内容:

    1. 加载类的 “结构信息

    把类的定义(比如类名、父类、方法列表)加载到内存,让程序知道 “这个类是什么样的”。

    • 比如程序会记录:ElementBase的父类是object(默认),它有locatorcache这些类属性,有__init__这个实例方法。

    2. 初始化 “类属性”(静态属性)

    类中定义的类属性(不是实例属性)会在此时被创建并赋值。

    • 比如你的ElementBase中:

      class ElementBase(metaclass=ElementMeta):locator = None  # 类属性cache = False   # 类属性page_container_loc = Locator(...)  # 类属性
      

      类初始化时,会给locatorcachepage_container_loc这些属性分配内存,并设置初始值(NoneFalseLocator对象)。
    • 这些类属性属于类本身,所有实例共享(比如ElementBase.cacheobj.cache在未被实例覆盖时是同一个值)。
    3. 执行类级别的代码块(如果有)

    如果类中存在直接写在类里的代码(不是方法内的),会在此时执行。

    • 比如:
      class ElementBase:print("正在初始化ElementBase类")  # 类级代码块cache = False
      

      类初始化时,会先打印这句话,再给cache赋值。

    4. 处理元类(如果有)

    你的类用了metaclass=ElementMeta,元类会在类初始化时 “干预” 类的创建过程。

    • 元类可以动态修改类的属性、方法,甚至决定类是否能被创建(比如校验类属性是否符合规范)。
    • 这一步是 Python 特有的,比如ElementMeta可能会在类加载时自动处理locator属性的格式。

    5. 初始化静态方法 / 类方法(如果有)

    静态方法(@staticmethod)和类方法(@classmethod)属于类本身,会在类初始化时加载到内存,此时就可以通过 “类名。方法名” 调用(比如ElementBase.static_method())。

    【3】和 “实例初始化” 的区别(关键!)

    很多人会混淆 “类初始化” 和 “实例初始化”,这里明确:

    类初始化(类加载)实例初始化(__init__方法)
    针对 “类本身”,只执行 1 次针对 “每个实例”,每次new/创建实例时执行
    加载类属性、静态方法等初始化实例属性(比如self.element = None
    完成后类就 “可用” 了完成后得到一个具体的实例对象

    比如你的ElementBase

    • 当第一次用到ElementBase时(比如obj = ElementBase()),先触发类初始化(加载locator等类属性);
    • 类初始化完成后,才会执行__init__方法,创建obj这个实例,初始化self.element实例属性

    http://www.dtcms.com/a/344655.html

    相关文章:

  • 数据结构 -- 树
  • Vue3+Ant-design-vue+SSE实现实时进度条
  • 前端快讯看这里
  • 基于导频的OFDM系统的信道估计(使用LS估计算法)
  • 突击复习清单(高频核心考点)
  • 【C++高阶六】哈希与哈希表
  • 线程池拒绝策略踩坑
  • uniappx与uniapp的区别
  • 【UniApp打包鸿蒙APP全流程】如何配置并添加UniApp API所需的鸿蒙系统权限
  • MySQL B+树索引使用
  • QT之QSS的使用方法和常用控件的样式设置
  • Qt 的事件类QEvent及其他子类事件的开发详解:从基础到实践的全方位指南
  • 高并发用户数峰值对系统架构设计有哪些影响?
  • Qt-窗口类部件
  • 极验demo(float)(一)
  • 数据结构:队列 二叉树
  • vivo“空间计算-机器人”生态落下关键一子
  • 码蹄杯进阶
  • 笔试——Day46
  • 基于SpringBoot+Vue框架的高校论坛系统 博客论坛系统 论坛小程序
  • 企业版Idea 无快捷键的启动方式
  • 和AI Agent一起读论文——A SURVEY OF S ELF EVOLVING A GENTS(五)
  • 如何监控和管理微服务之间的调用关系
  • 微信开发者工具:更改 AppID 失败
  • Unreal Engine Class System
  • 滑动窗口+子串+普通数组算法
  • Spring AI调用本地大模型实战
  • 【LINUX】CentOS7在VMware15中,从命令行界面切换到图形界面的异常汇总
  • Day10 Go语言深入学习(2)
  • 零成本 Redis 实战:用Amazon免费套餐练手 + 缓存优化