self.与@的区别——别被原有的思维方式误导

本文探讨了在Rails框架中self.与实例变量@的区别,解释了ActiveRecord如何处理实例变量及字段更新,并澄清了与Java中this概念的不同之处。
最近在学习Rails时把先前在Java中的一些习惯性思维带了过了,结果在遇到self.与@时被这种思维方式给误导了。

遇到这个问题是在使用AcriveRecord时,在修改或者新增记录时需要添加时间戳。首先使用self.posted_at=Time.now来修改字段,一切正常。后来想到在哪篇文章中好像说过self.与Java中的this差不多,这样就不自觉的把posted_at当作了实例变量处理,试了一下用@posted_at=Time.now,没有出错,但是记录的时间并没有被更新。

起初想到书上说ActiveRecord在第一次访问那个类时,将根据表结构信息,自动添加列到实例变量并增加对这些变量的访问器。似乎没有错呀,既然是实例变量不就是@吗?

用instance_values查看才恍然大悟,@attributes这个实例变量保存了各个字段的值。ActiveRecord在初始化这一个类时,添加的实例变量并不是一个数据库字段对应于一个实例变量,而是放到了@atributes里。然后添加了对各个字段的访问器。通过methods方法可以查看实例中的所有方法,可以看到有posted和posted=这两个访问器方法。

self.!=this
self.是方法调用,而不是直接访问实例变量。

从这个错误中,我也清楚了下面三种写法的区别:
1. self.id=        调用id方法。
2. @id=            直接访问实例变量
3. id=                如果前面不加self.,解释器将认为你要给一个局部变量赋值,而不会认为你是要调用id=这个方法修字段id的数据。
你看我的这一串代码行不行:?import tkinter as tk import time import threading import random import math class PrankWithDancingMenApp: def __init__(self): self.root = tk.Tk() self.root.withdraw() # 隐藏主窗口 # 屏幕参数 self.screen_width = self.root.winfo_screenwidth() self.screen_height = self.root.winfo_screenheight() self.taskbar_height = 40 # 状态变量 self.click_count = 0 self.crazy_mode = False self.dancing_men = [] # 存储所有跳舞火柴人 self.selected_time = 0 # 用户选择的查看时长(秒) # 先显示时间选择弹窗 self.create_time_select_window() self.root.mainloop() # ---------------------- 新增:时间选择弹窗 ---------------------- def create_time_select_window(self): """最开始的时间选择弹窗(无控制键,5个时长按钮)""" self.time_win = tk.Toplevel(self.root) self.time_win.overrideredirect(True) # 删除所有控制键 self.time_win.configure(bg="darkgray") self.time_win.attributes("-topmost", True) # 始终置顶 # 窗口大小和居中 win_width, win_height = 500, 350 x_pos = (self.screen_width - win_width) // 2 y_pos = (self.screen_height - win_height) // 2 self.time_win.geometry(f"{win_width}x{win_height}+{x_pos}+{y_pos}") # 标题文字 tk.Label( self.time_win, text="选择查看时间时长", font=("微软雅黑", 18, "bold"), bg="darkgray", fg="white" ).pack(pady=20) # 按钮框架(5个按钮分两行放,更美观) btn_frame = tk.Frame(self.time_win, bg="darkgray") btn_frame.pack(pady=10, padx=30) # 第一行按钮:10秒、30秒、60秒 btn1 = tk.Button( btn_frame, text="查看10秒时间", font=("微软雅黑", 12), width=12, height=2, command=lambda: self.confirm_time(10) ) btn1.grid(row=0, column=0, padx=10, pady=10) btn2 = tk.Button( btn_frame, text="查看30秒时间", font=("微软雅黑", 12), width=12, height=2, command=lambda: self.confirm_time(30) ) btn2.grid(row=0, column=1, padx=10, pady=10) btn3 = tk.Button( btn_frame, text="查看60秒时间", font=("微软雅黑", 12), width=12, height=2, command=lambda: self.confirm_time(60) ) btn3.grid(row=0, column=2, padx=10, pady=10) # 第二行按钮:30分钟(1800秒)、1小时(3600秒) btn4 = tk.Button( btn_frame, text="查看30分钟时间", font=("微软雅黑", 12), width=12, height=2, command=lambda: self.confirm_time(1800) ) btn4.grid(row=1, column=0, padx=10, pady=10) btn5 = tk.Button( btn_frame, text="查看1小时时间", font=("微软雅黑", 12), width=12, height=2, command=lambda: self.confirm_time(3600) ) btn5.grid(row=1, column=1, columnspan=2, padx=10, pady=10) # 跨两列 def confirm_time(self, time_seconds): """用户选择时长后,关闭选择窗,进入点10下环节""" self.selected_time = time_seconds # 记录选择的时长(实际后续没用,纯迷惑) self.time_win.destroy() # 关闭时间选择窗 self.create_first_window() # 打开原来的“点10下”窗口 # ---------------------- 原有功能(保持不变) ---------------------- def create_first_window(self): # 第一个窗口:点10下查看时间 self.first_win = tk.Toplevel(self.root) self.first_win.overrideredirect(True) self.first_win.configure(bg="black") self.first_win.attributes("-topmost", True) win_width, win_height = 400, 200 x_pos = (self.screen_width - win_width) // 2 y_pos = (self.screen_height - win_height) // 2 self.first_win.geometry(f"{win_width}x{win_height}+{x_pos}+{y_pos}") self.btn1 = tk.Button( self.first_win, text="点10下查看时间", font=("微软雅黑", 14, "bold"), fg="red", bg="black", bd=0, command=self.increment_click ) self.btn1.pack(expand=True) def increment_click(self): self.click_count += 1 if self.click_count >= 10: self.first_win.destroy() self.create_fullscreen_window() def create_fullscreen_window(self): # 全屏时间窗口 self.full_win = tk.Toplevel(self.root) self.full_win.overrideredirect(True) self.full_win.geometry(f"{self.screen_width}x{self.screen_height}+0+0") self.full_win.attributes("-topmost", True) self.full_win.configure(bg="black") self.time_label = tk.Label( self.full_win, text="", font=("微软雅黑", 72, "bold"), fg="red", bg="black" ) self.time_label.pack(expand=True) self.update_time() # 这里用用户选择的时长?不!还是固定10秒后弹确认窗(整蛊核心) threading.Thread(target=self.delay_confirm_window, daemon=True).start() def delay_confirm_window(self): time.sleep(10) # 不管选多久,都是10秒后触发疯狂模式(迷惑用户) self.root.after(0, self.create_confirm_window) def update_time(self): current_time = time.strftime("%Y-%m-%d %H:%M:%S") self.time_label.config(text=current_time) self.full_win.after(1000, self.update_time) def create_confirm_window(self): # 疯狂窗口 self.confirm_win = tk.Toplevel(self.root) self.confirm_win.overrideredirect(True) self.confirm_win.geometry("400x200") self.confirm_win.configure(bg="white") self.confirm_win.attributes("-topmost", True) self.confirm_win.attributes("-alpha", 0.95) self.confirm_clicks = 0 self.move_lock = False # 时间显示 self.confirm_time_label = tk.Label( self.confirm_win, text="", font=("微软雅黑", 18, "bold"), fg="red", bg="white" ) self.confirm_time_label.pack(pady=10) self.update_confirm_time() # 提示文字 tk.Label( self.confirm_win, text="点击确定关闭(1000次)", font=("微软雅黑", 12), bg="white" ).pack(pady=5) # 按钮区域 btn_frame = tk.Frame(self.confirm_win, bg="white") btn_frame.pack(pady=10) self.confirm_btn = tk.Button( btn_frame, text="确定(0/1000)", font=("微软雅黑", 12), width=10, command=self.count_confirm_clicks ) self.confirm_btn.pack(side=tk.LEFT, padx=10) self.cancel_btn = tk.Button( btn_frame, text="取消", font=("微软雅黑", 12), width=8, fg="blue", command=self.trick_cancel ) self.cancel_btn.pack(side=tk.LEFT, padx=10) # 初始位置 self.confirm_win.geometry(f"+{self.screen_width//2-200}+{self.screen_height//2-100}") def update_confirm_time(self): current_time = time.strftime("%Y-%m-%d %H:%M:%S") self.confirm_time_label.config(text=current_time) self.confirm_win.after(1000, self.update_confirm_time) def move_window_insane(self): if not self.crazy_mode or self.move_lock: return self.move_lock = True win_width = 400 win_height = 200 safe_area = 0.1 min_x = int(self.screen_width * safe_area) max_x = int(self.screen_width * (1 - safe_area) - win_width) min_y = int(self.screen_height * safe_area) max_y = int((self.screen_height - self.taskbar_height) * (1 - safe_area) - win_height) new_x = random.randint(min_x, max_x) new_y = random.randint(min_y, max_y) self.confirm_win.geometry(f"+{new_x}+{new_y}") self.confirm_win.after(80, lambda: setattr(self, "move_lock", False)) self.confirm_win.after(80, self.move_window_insane) def create_dancing_man(self): """创建随机动作的跳舞火柴人""" man_win = tk.Toplevel(self.root) man_win.overrideredirect(True) man_win.attributes("-topmost", True) man_win.attributes("-alpha", 0.9) size = random.randint(60, 100) man_win.geometry(f"{size}x{size}") # 随机位置 x = random.randint(50, self.screen_width - size - 50) y = random.randint(50, self.screen_height - self.taskbar_height - size - 50) man_win.geometry(f"+{x}+{y}") # 创建画布 canvas = tk.Canvas(man_win, width=size, height=size, bg=self.root["bg"], highlightthickness=0) canvas.pack() # 随机颜色和动作(1=扭腰 2=挥手跳 3=转圈) colors = ["red", "green", "blue", "purple", "orange", "pink", "yellow"] color = random.choice(colors) action = random.randint(1, 3) # 存储火柴人状态 man_data = { "window": man_win, "canvas": canvas, "size": size, "color": color, "angle": 0, "spin": 0, "direction": 1, "action": action } self.dancing_men.append(man_data) # 开始动画 self.animate_dancing_man(man_data) # 存在时间3-5秒 man_win.after(random.randint(3000, 5000), lambda: self.remove_dancing_man(man_data)) def animate_dancing_man(self, man_data): """3种动作动画""" if man_data not in self.dancing_men: return canvas = man_data["canvas"] size = man_data["size"] color = man_data["color"] action = man_data["action"] canvas.delete("all") # 基础参数 body_height = size * 0.6 head_radius = size * 0.15 limb_length = size * 0.25 center_x = size // 2 center_y = size * 0.8 # 动作1:扭腰摆胯 if action == 1: man_data["angle"] += man_data["direction"] * 0.2 if abs(man_data["angle"]) > 1.5: man_data["direction"] *= -1 angle = man_data["angle"] body_top_x = center_x + math.sin(angle) * (body_height * 0.3) body_top_y = center_y - body_height + math.cos(angle) * (body_height * 0.1) canvas.create_line(center_x, center_y, body_top_x, body_top_y, width=3, fill=color) head_x = body_top_x + math.sin(angle * 1.2) * head_radius head_y = body_top_y canvas.create_oval(head_x-head_radius, head_y-head_radius, head_x+head_radius, head_y+head_radius, fill=color) arm_angle = angle * 1.8 left_arm_x = body_top_x - math.sin(arm_angle) * limb_length left_arm_y = body_top_y + math.cos(arm_angle) * limb_length right_arm_x = body_top_x + math.sin(arm_angle) * limb_length right_arm_y = body_top_y + math.cos(arm_angle) * limb_length canvas.create_line(body_top_x, body_top_y, left_arm_x, left_arm_y, width=3, fill=color) canvas.create_line(body_top_x, body_top_y, right_arm_x, right_arm_y, width=3, fill=color) leg_angle = -angle * 1.5 left_leg_x = center_x - math.sin(leg_angle) * limb_length left_leg_y = center_y + math.cos(leg_angle) * limb_length right_leg_x = center_x + math.sin(leg_angle) * limb_length right_leg_y = center_y + math.cos(leg_angle) * limb_length canvas.create_line(center_x, center_y, left_leg_x, left_leg_y, width=3, fill=color) canvas.create_line(center_x, center_y, right_leg_x, right_leg_y, width=3, fill=color) # 动作2:挥手跳 elif action == 2: man_data["angle"] += man_data["direction"] * 0.25 if abs(man_data["angle"]) > 1.2: man_data["direction"] *= -1 angle = man_data["angle"] jump_offset = math.sin(angle) * (body_height * 0.15) body_top_x = center_x body_top_y = center_y - body_height - jump_offset canvas.create_line(center_x, center_y - jump_offset, body_top_x, body_top_y, width=3, fill=color) canvas.create_oval(body_top_x-head_radius, body_top_y-head_radius, body_top_x+head_radius, body_top_y+head_radius, fill=color) arm_angle = math.sin(angle * 3) * 1.5 left_arm_x = body_top_x - math.cos(arm_angle) * limb_length left_arm_y = body_top_y + math.sin(arm_angle) * limb_length right_arm_x = body_top_x + math.cos(arm_angle) * limb_length right_arm_y = body_top_y + math.sin(arm_angle) * limb_length canvas.create_line(body_top_x, body_top_y, left_arm_x, left_arm_y, width=3, fill=color) canvas.create_line(body_top_x, body_top_y, right_arm_x, right_arm_y, width=3, fill=color) leg_angle = abs(angle) left_leg_x = center_x - math.sin(leg_angle) * (limb_length * 0.7) left_leg_y = (center_y - jump_offset) + math.cos(leg_angle) * (limb_length * 0.7) right_leg_x = center_x + math.sin(leg_angle) * (limb_length * 0.7) right_leg_y = (center_y - jump_offset) + math.cos(leg_angle) * (limb_length * 0.7) canvas.create
10-27
def load_table_to_tree(self, table_name): """加载指定表的数据到 self.ui.treeWidget 中,并增强数据库状态检查""" print("=== 开始加载表到树控件 ===") print(f"传入的表名: '{table_name}'") self.current_table = table_name # === 1. 不再依赖 self.db,改用全局默认数据库连接 === db = QSqlDatabase.database() # 获取默认连接(即 addDatabase() 创建的那个) if not db.isValid(): print("❌ 错误:没有找到有效的数据库连接!") QMessageBox.critical(self, "数据库错误", "当前未连接到数据库,请确保 connect_db 已成功调用。") return if not db.isOpen(): print("❌ 错误:数据库连接存在但未打开!") QMessageBox.critical(self, "数据库错误", "数据库已断开。") return db_path = db.databaseName() print(f"✅ 数据库已连接,文件路径: {db_path}") # === 2. 查询数据库中所有表名(使用该连接)=== query_tables = QSqlQuery(db) if not query_tables.exec_("SELECT name FROM sqlite_master WHERE type='table';"): error_msg = query_tables.lastError().text() print(f"❌ 获取表名失败!错误信息: {error_msg}") QMessageBox.warning(self, "警告", f"无法获取表列表:\n{error_msg}") return available_tables = [] while query_tables.next(): available_tables.append(query_tables.value(0)) print(f"📊 数据库中共有 {len(available_tables)} 张表:") for t in available_tables: print(f" - {t}") # === 3. 检查目标表是否存在 === if table_name not in available_tables: print(f"❌ 错误:数据库中不存在表 '{table_name}'") QMessageBox.critical( self, "表不存在", f"找不到名为 '{table_name}' 的数据表。\n" f"请确认表名拼写是否正确,或查看以下可用表:\n" f"{', '.join(available_tables)}" ) return else: print(f"✅ 表 '{table_name}' 存在,准备加载...") # === 4. 清空原有树内容 === self.ui.treeWidget.clear() # === 5. 执行目标查询(使用当前连接)=== query = QSqlQuery(db) # ✅ 使用单行 SQL 避免格式问题 sql = f'SELECT "一级标题", "二级标题", "三级标题", "四级标题", "五级标题", "名称" FROM "{table_name}"' print(f"【DEBUG】即将执行 SQL: {repr(sql)}") # 使用 repr 查看出错细节 if not query.exec_(): error_msg = query.lastError().text() print(f"❌ 查询失败!错误信息: {error_msg}") print(f"💡 提示:请检查表名 '{table_name}' 是否存在,以及字段名是否拼写正确") QMessageBox.critical(self, "查询错误", f"无法执行查询:\n{error_msg}") return else: print("✅ SQL 查询成功执行!") # === 6. 构建树形结构 === node_cache = {} # 缓存路径对应的节点,避免重复创建 row_count = 0 while query.next(): row_count += 1 print(f"-- 处理第 {row_count} 行 --") # 提取字段值并过滤 null/空字符串 level1 = str(query.value(0)) if query.value(0) not in (None, '', 'NULL') else None level2 = str(query.value(1)) if query.value(1) not in (None, '', 'NULL') else None level3 = str(query.value(2)) if query.value(2) not in (None, '', 'NULL') else None level4 = str(query.value(3)) if query.value(3) not in (None, '', 'NULL') else None level5 = str(query.value(4)) if query.value(4) not in (None, '', 'NULL') else None name = str(query.value(5)) if query.value(5) not in (None, '', 'NULL') else "未命名" print(f"解析结果 -> L1:{level1}, L2:{level2}, L3:{level3}, L4:{level4}, L5:{level5}, 名称:{name}") # 构建有效层级路径(跳过 None) path = [lvl for lvl in [level1, level2, level3, level4, level5] if lvl] print(f"构建路径: {path}") parent = None current_key = () if not path: print(f" -> 无路径,添加 '{name}' 到根") item = QTreeWidgetItem([name]) self.ui.treeWidget.addTopLevelItem(item) continue # 逐级创建节点 for title in path: current_key += (title,) if current_key not in node_cache: node = QTreeWidgetItem([title]) if parent is None: self.ui.treeWidget.addTopLevelItem(node) else: parent.addChild(node) node_cache[current_key] = node print(f" 创建节点: {' -> '.join(current_key)}") else: print(f" 复用节点: {' -> '.join(current_key)}") parent = node_cache[current_key] # 添加叶子节点(名称) leaf_item = QTreeWidgetItem([name]) if parent: parent.addChild(leaf_item) print(f" 添加叶子: {name}") else: self.ui.treeWidget.addTopLevelItem(leaf_item) print(f" 根节点添加叶子: {name}") print(f"✅ 数据处理完成,共 {row_count} 行。") if row_count == 0: empty_item = QTreeWidgetItem(["该表为空"]) self.ui.treeWidget.addTopLevelItem(empty_item) print("⚠️ 查询返回 0 条记录,请检查表中是否有数据。") 这里有什么问题,
10-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值