Python设计模式深度解析:装饰器模式(Decorator Pattern)完全指南

Python设计模式深度解析:装饰器模式(Decorator Pattern)完全指南

    • 前言
    • 什么是装饰器模式?
      • 装饰器模式的核心思想
    • Python函数装饰器:从基础到高级
      • 基础函数装饰器
      • 高级函数装饰器实现
    • GUI装饰器模式:动态界面增强
      • Tkinter按钮装饰器
    • 类装饰器:元编程的力量
      • 数据类装饰器对比
      • 自定义类装饰器
    • 装饰器模式 vs Python装饰器语法
      • 相同点
      • 不同点
    • 实际应用场景
      • Web开发中的装饰器
      • 性能监控装饰器
    • 最佳实践和注意事项
      • 1. 保持接口一致性
      • 2. 使用functools.wraps保持元数据
      • 3. 考虑装饰器的顺序
    • 总结
      • 关键要点
      • 选择指南

前言

在软件开发中,我们经常需要在不修改原有代码的情况下为对象添加新功能。传统的继承方式虽然可以实现功能扩展,但会导致类的数量急剧增加,且缺乏灵活性。装饰器模式(Decorator Pattern)为我们提供了一种更优雅的解决方案,它允许我们动态地为对象添加功能,而无需修改其结构。

本文将通过实际代码示例,深入讲解Python中装饰器模式的实现方式、应用场景以及与Python内置装饰器语法的关系。

什么是装饰器模式?

装饰器模式是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

装饰器模式的核心思想

  1. 组合优于继承:通过对象组合而非继承来扩展功能
  2. 透明性:装饰器与被装饰对象具有相同的接口
  3. 动态性:可以在运行时动态地添加或移除功能
  4. 可组合性:多个装饰器可以组合使用

Python函数装饰器:从基础到高级

基础函数装饰器

让我们从一个简单的函数装饰器开始:

def mathFunc(func):"""基础装饰器函数"""def wrapper(x):print("b4 func")  # 函数执行前func(x)           # 执行原函数print("after func")  # 函数执行后return wrapper# 方式1:手动应用装饰器
def sayMath(x):print("math")sayMath = mathFunc(sayMath)  # 手动装饰
sayMath(12)# 方式2:使用@语法糖
@mathFunc
def sayMath2(x):print("math")sayMath2(12)

这个例子展示了装饰器的基本工作原理:

  1. 装饰器函数接收一个函数作为参数
  2. 返回一个新的函数(wrapper)
  3. 新函数在调用原函数前后添加额外功能

高级函数装饰器实现

让我们实现一些更实用的装饰器:

import time
import functools
from typing import Any, Callabledef timer(func: Callable) -> Callable:"""计时装饰器"""@functools.wraps(func)def wrapper(*args, **kwargs):start_time = time.time()result = func(*args, **kwargs)end_time = time.time()print(f"{func.__name__} 执行时间: {end_time - start_time:.4f}秒")return resultreturn wrapperdef logger(func: Callable) -> Callable:"""日志装饰器"""@functools.wraps(func)def wrapper(*args, **kwargs):print(f"调用函数: {func.__name__}")print(f"参数: args={args}, kwargs={kwargs}")result = func(*args, **kwargs)print(f"返回值: {result}")return resultreturn wrapperdef retry(max_attempts: int = 3, delay: float = 1):"""重试装饰器(参数化装饰器)"""def decorator(func: Callable) -> Callable:@functools.wraps(func)def wrapper(*args, **kwargs):for attempt in range(max_attempts):try:return func(*args, **kwargs)except Exception as e:if attempt == max_attempts - 1:raise eprint(f"第{attempt + 1}次尝试失败: {e}")time.sleep(delay)return wrapperreturn decoratordef cache(func: Callable) -> Callable:"""缓存装饰器"""cache_dict = {}@functools.wraps(func)def wrapper(*args, **kwargs):# 创建缓存键key = str(args) + str(sorted(kwargs.items()))if key in cache_dict:print(f"缓存命中: {func.__name__}")return cache_dict[key]result = func(*args, **kwargs)cache_dict[key] = resultprint(f"缓存存储: {func.__name__}")return resultreturn wrapper# 使用装饰器的示例
@timer
@logger
def calculate_sum(n: int) -> int:"""计算1到n的和"""return sum(range(1, n + 1))@cache
@timer
def fibonacci(n: int) -> int:"""计算斐波那契数列"""if n <= 1:return nreturn fibonacci(n - 1) + fibonacci(n - 2)@retry(max_attempts=3, delay=0.5)
def unreliable_network_call():"""模拟不可靠的网络调用"""import randomif random.random() < 0.7:  # 70%失败率raise Exception("网络连接失败")return "数据获取成功"# 测试装饰器
def test_decorators():print("=== 计时和日志装饰器 ===")result = calculate_sum(1000)print("\n=== 缓存装饰器 ===")print("第一次计算斐波那契:")fib_result = fibonacci(10)print("第二次计算斐波那契:")fib_result = fibonacci(10)  # 使用缓存print("\n=== 重试装饰器 ===")try:result = unreliable_network_call()print(f"网络调用成功: {result}")except Exception as e:print(f"网络调用最终失败: {e}")if __name__ == "__main__":test_decorators()

GUI装饰器模式:动态界面增强

Tkinter按钮装饰器

基于您的代码,让我们看看如何在GUI中应用装饰器模式:

from tkinter import *class Decorator(Button):"""按钮装饰器基类"""def __init__(self, master, **kwargs):super().__init__(master, **kwargs)# 默认设置为平面样式self.configure(relief=FLAT)# 绑定鼠标事件self.bind("<Enter>", self.on_enter)self.bind("<Leave>", self.on_leave)def on_enter(self, evt):"""鼠标进入时的效果"""self.configure(relief=RAISED)def on_leave(self, evt):"""鼠标离开时的效果"""self.configure(relief=FLAT)class HoverButton(Decorator):"""悬停效果按钮"""def __init__(self, master, text="按钮", **kwargs):super().__init__(master, text=text, **kwargs)class ClickCountButton(Decorator):"""点击计数按钮装饰器"""def __init__(self, master, text="按钮", **kwargs):super().__init__(master, **kwargs)self.click_count = 0self.original_text = textself.configure(text=f"{text} (0)")self.configure(command=self.on_click)def on_click(self):"""点击事件处理"""self.click_count += 1self.configure(text=f"{self.original_text} ({self.click_count})")class ColorChangeButton(Decorator):"""颜色变化按钮装饰器"""def __init__(self, master, text="按钮", **kwargs):super().__init__(master, text=text, **kwargs)self.colors = ['lightblue', 'lightgreen', 'lightcoral', 'lightyellow']self.color_index = 0self.configure(bg=self.colors[0])self.configure(command=self.change_color)def change_color(self):"""改变按钮颜色"""self.color_index = (self.color_index + 1) % len(self.colors)self.configure(bg=self.colors[self.color_index])# GUI应用示例
class DecoratorGUIDemo:def __init__(self):self.root = Tk()self.root.title("装饰器模式GUI演示")self.root.geometry("400x300")self.create_widgets()def create_widgets(self):"""创建界面组件"""Label(self.root, text="装饰器模式按钮演示", font=("Arial", 16)).pack(pady=10)# 基础悬停按钮hover_btn = HoverButton(self.root, "悬停效果按钮")hover_btn.pack(pady=5)# 点击计数按钮count_btn = ClickCountButton(self.root, "点击计数按钮")count_btn.pack(pady=5)# 颜色变化按钮color_btn = ColorChangeButton(self.root, "颜色变化按钮")color_btn.pack(pady=5)# 组合装饰器按钮combo_btn = self.create_combo_button()combo_btn.pack(pady=5)# 退出按钮Button(self.root, text="退出", command=self.root.quit).pack(pady=20)def create_combo_button(self):"""创建组合装饰器按钮"""class ComboButton(ClickCountButton, ColorChangeButton):def __init__(self, master, text="组合按钮", **kwargs):# 多重继承需要小心处理Decorator.__init__(self, master, text=text, **kwargs)self.click_count = 0self.original_text = textself.colors = ['lightblue', 'lightgreen', 'lightcoral', 'lightyellow']self.color_index = 0self.configure(text=f"{text} (0)", bg=self.colors[0])self.configure(command=self.on_combo_click)def on_combo_click(self):"""组合点击事件"""self.click_count += 1self.color_index = (self.color_index + 1) % len(self.colors)self.configure(text=f"{self.original_text} ({self.click_count})",bg=self.colors[self.color_index])return ComboButton(self.root, "组合装饰器按钮")def run(self):"""运行应用"""self.root.mainloop()# 运行GUI演示
if __name__ == "__main__":app = DecoratorGUIDemo()app.run()

类装饰器:元编程的力量

数据类装饰器对比

让我们对比传统类定义和使用@dataclass装饰器的区别:

# 传统类定义(基于您的dclasse.py)
class Employee:def __init__(self, frname: str, lname: str, idnum: int,town='Stamford', state='CT', zip='06820'):self.frname = frnameself.lname = lnameself.idnum = idnumself.town = townself.state = stateself.zip = zipdef nameString(self):return f"{self.frname} {self.lname} {self.idnum}"def __repr__(self):return f"Employee({self.frname}, {self.lname}, {self.idnum})"def __eq__(self, other):if not isinstance(other, Employee):return Falsereturn (self.frname == other.frname and self.lname == other.lname and self.idnum == other.idnum)# 使用@dataclass装饰器(基于您的dclass.py)
from dataclasses import dataclass@dataclass
class EmployeeDataClass:frname: strlname: stridnum: inttown: str = "Stamford"state: str = 'CT'zip: str = '06820'def nameString(self):return f"{self.frname} {self.lname} {self.idnum}"# 对比测试
def compare_implementations():"""对比两种实现方式"""print("=== 传统类实现 ===")emp1 = Employee('Sarah', 'Smythe', 123)emp2 = Employee('Sarah', 'Smythe', 123)print(f"emp1: {emp1}")print(f"emp1 == emp2: {emp1 == emp2}")print(f"emp1.nameString(): {emp1.nameString()}")print("\n=== @dataclass实现 ===")emp3 = EmployeeDataClass('Sarah', 'Smythe', 123)emp4 = EmployeeDataClass('Sarah', 'Smythe', 123)print(f"emp3: {emp3}")print(f"emp3 == emp4: {emp3 == emp4}")  # 自动生成__eq__print(f"emp3.nameString(): {emp3.nameString()}")if __name__ == "__main__":compare_implementations()

自定义类装饰器

让我们实现一些实用的类装饰器:

def singleton(cls):"""单例装饰器"""instances = {}def get_instance(*args, **kwargs):if cls not in instances:instances[cls] = cls(*args, **kwargs)return instances[cls]return get_instancedef add_repr(cls):"""添加__repr__方法的装饰器"""def __repr__(self):attrs = ', '.join(f'{k}={v!r}' for k, v in self.__dict__.items())return f"{cls.__name__}({attrs})"cls.__repr__ = __repr__return clsdef validate_types(**type_validators):"""类型验证装饰器"""def decorator(cls):original_setattr = cls.__setattr__def new_setattr(self, name, value):if name in type_validators:expected_type = type_validators[name]if not isinstance(value, expected_type):raise TypeError(f"{name} must be of type {expected_type.__name__}, "f"got {type(value).__name__}")original_setattr(self, name, value)cls.__setattr__ = new_setattrreturn clsreturn decoratordef auto_property(*attr_names):"""自动属性装饰器"""def decorator(cls):for attr_name in attr_names:private_name = f"_{attr_name}"def make_property(name, private):def getter(self):return getattr(self, private, None)def setter(self, value):setattr(self, private, value)return property(getter, setter)setattr(cls, attr_name, make_property(attr_name, private_name))return clsreturn decorator# 使用类装饰器的示例
@singleton
@add_repr
class DatabaseConnection:def __init__(self, host="localhost", port=5432):self.host = hostself.port = portself.connected = Falseprint(f"创建数据库连接: {host}:{port}")def connect(self):self.connected = Trueprint("连接到数据库")@validate_types(name=str, age=int, salary=float)
@add_repr
class Person:def __init__(self, name, age, salary):self.name = nameself.age = ageself.salary = salary@auto_property('name', 'age')
class Student:def __init__(self, name, age):self.name = name  # 会调用setterself.age = age    # 会调用setter# 测试类装饰器
def test_class_decorators():print("=== 单例装饰器测试 ===")db1 = DatabaseConnection()db2 = DatabaseConnection("remote", 3306)print(f"db1 is db2: {db1 is db2}")  # True,单例模式print(f"db1: {db1}")print("\n=== 类型验证装饰器测试 ===")try:person = Person("Alice", 25, 50000.0)print(f"person: {person}")person.age = "invalid"  # 会抛出TypeErrorexcept TypeError as e:print(f"类型验证失败: {e}")print("\n=== 自动属性装饰器测试 ===")student = Student("Bob", 20)print(f"student.name: {student.name}")print(f"student._name: {student._name}")  # 私有属性if __name__ == "__main__":test_class_decorators()

装饰器模式 vs Python装饰器语法

相同点

  1. 功能增强:都用于为对象或函数添加额外功能
  2. 透明性:都保持原有接口不变
  3. 组合性:都可以组合使用

不同点

  1. 应用层面

    • 装饰器模式:主要用于对象级别的功能扩展
    • Python装饰器:主要用于函数和类的元编程
  2. 实现方式

    • 装饰器模式:通过类的组合和继承
    • Python装饰器:通过函数的高阶特性
  3. 运行时行为

    • 装饰器模式:可以在运行时动态添加/移除装饰器
    • Python装饰器:在定义时就确定了装饰关系

实际应用场景

Web开发中的装饰器

# Flask风格的路由装饰器
def route(path):def decorator(func):# 注册路由app.routes[path] = funcreturn funcreturn decorator# 权限验证装饰器
def require_auth(func):@functools.wraps(func)def wrapper(*args, **kwargs):if not current_user.is_authenticated:raise PermissionError("需要登录")return func(*args, **kwargs)return wrapper# 使用示例
@route('/api/users')
@require_auth
def get_users():return {"users": ["Alice", "Bob"]}

性能监控装饰器

import psutil
import threadingdef monitor_performance(func):"""性能监控装饰器"""@functools.wraps(func)def wrapper(*args, **kwargs):# 记录开始状态start_memory = psutil.Process().memory_info().rssstart_time = time.time()try:result = func(*args, **kwargs)return resultfinally:# 记录结束状态end_memory = psutil.Process().memory_info().rssend_time = time.time()print(f"函数 {func.__name__} 性能报告:")print(f"  执行时间: {end_time - start_time:.4f}秒")print(f"  内存变化: {(end_memory - start_memory) / 1024 / 1024:.2f}MB")return wrapper@monitor_performance
def heavy_computation():"""重计算任务"""data = [i ** 2 for i in range(1000000)]return sum(data)

最佳实践和注意事项

1. 保持接口一致性

# 好的做法:保持接口一致
class TextProcessor:def process(self, text):return textclass UpperCaseDecorator:def __init__(self, processor):self._processor = processordef process(self, text):  # 保持相同的方法签名return self._processor.process(text).upper()# 不好的做法:改变接口
class BadDecorator:def __init__(self, processor):self._processor = processordef process_text(self, text):  # 改变了方法名return self._processor.process(text).upper()

2. 使用functools.wraps保持元数据

import functoolsdef good_decorator(func):@functools.wraps(func)  # 保持原函数的元数据def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapperdef bad_decorator(func):def wrapper(*args, **kwargs):  # 丢失原函数的元数据return func(*args, **kwargs)return wrapper@good_decorator
def example_function():"""这是一个示例函数"""passprint(example_function.__name__)  # 输出: example_function
print(example_function.__doc__)   # 输出: 这是一个示例函数

3. 考虑装饰器的顺序

@timer
@logger
@cache
def complex_function(n):"""复杂函数"""# 执行顺序:cache -> logger -> timer -> complex_functionreturn sum(range(n))# 等价于:
# complex_function = timer(logger(cache(complex_function)))

总结

装饰器模式是一种强大的设计模式,它提供了比继承更灵活的功能扩展方式。在Python中,我们既可以使用传统的面向对象方式实现装饰器模式,也可以利用Python的装饰器语法来实现类似的功能。

关键要点

  1. 组合优于继承:装饰器模式通过组合来扩展功能
  2. 透明性:装饰器与被装饰对象具有相同接口
  3. 灵活性:可以动态地添加、移除或组合装饰器
  4. Python特色:充分利用Python的装饰器语法和元编程特性

选择指南

  • 对象功能扩展:使用传统的装饰器模式
  • 函数功能增强:使用Python函数装饰器
  • 类功能增强:使用Python类装饰器
  • 元编程需求:结合使用多种装饰器技术

通过本文的学习,相信您已经掌握了装饰器模式的精髓。在实际开发中,请根据具体场景选择合适的实现方式,并始终考虑代码的可读性和可维护性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/bicheng/89323.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM--虚拟线程

首先了解一个理念&#xff1a;线程与 OS 线程 1:1 绑定在传统 Java 线程&#xff08;平台线程&#xff09;模型中&#xff1a;每个 Java 线程直接对应一个操作系统级别的线程操作系统负责调度这些线程线程的创建、管理和调度都由操作系统内核处理这种模型称为 1:1 线程模型&…

掌握系统设计的精髓:12个核心设计模式的通俗解读

在构建复杂且高可用的软件系统时&#xff0c;仅仅了解编程语言和算法是不够的。真正的挑战在于如何设计出能够应对并发、故障、扩展等各种问题的健壮架构。系统设计模式正是前辈们在无数实践中提炼出的智慧结晶&#xff0c;它们是解决常见系统问题的“最佳实践”。 本文将深入浅…

概率论与数理统计(二)

事件的概率 概率&#xff1a;可能性的大小 古典概率模型&#xff1a; 1&#xff09;有限个样本点 2&#xff09;等可能性 P(A)A中包含的基本事件数基本事件总和 P(A) \frac{A中包含的基本事件数}{基本事件总和} P(A)基本事件总和A中包含的基本事件数​ 频率与概率 nnn 次实验…

新型eSIM攻击技术可克隆用户资料并劫持手机身份

eSIM技术存在重大安全漏洞研究人员发现eSIM技术中存在一个关键漏洞&#xff0c;攻击者可利用该漏洞克隆移动用户资料并劫持手机身份。AG安全研究团队宣布&#xff0c;他们成功攻破了采用GSMA消费者证书的Kigen eUICC&#xff08;嵌入式通用集成电路卡&#xff09;安全防护&…

langchain教程2:更加高级和灵活的Prompt模板

文章目录 prompt模板 对话Prompt模板 函数大师 使用jinja2与f-string实现提示词模板格式化 组合式提示词模板 prompt模板 from langchain.prompts import PromptTemplateprompt = PromptTemplate.from_template("你是一个{name},帮我起一个具有{country}特色的{gender}名…

UE5使用Motion Warping有什么用?

在 UE5 中&#xff0c;Motion Warping 是一套用于「动态调整根运动动画」的系统插件&#xff0c;它能让带有根运动&#xff08;Root Motion&#xff09;的动画根据游戏运行时的环境自动变形&#xff08;Warp&#xff09;&#xff0c;以更精准地贴合目标位置或目标方向&#xff…

类模版的相关案例

案例实现&#xff1a;实现一个通用的数组类&#xff0c;要求如下&#xff1a;可以对内置数据类型以及自定义数据类型的数据进行存储将数组中的数据存储到堆区构造函数中可以传入数组的容量提供对应的拷贝构造函数以及operator防止浅拷贝问题提供尾插法和尾删法对数组中的数据进…

服务器端安全检测与防御技术概述

一、服务器安全风险1.不必要的访问&#xff08;如只提供HTTP服务&#xff09;--应用识别控制2.公网发起IP或端口扫描、DDOS攻击等--防火墙3.漏洞攻击&#xff08;针对服务器操作系统等&#xff09;--IPS4.根据软件版本的已知漏洞进行攻击&#xff0c;口令暴力破解、获取用户权限…

前端性能与可靠性工程系列: 渲染、缓存与关键路径优化

前端性能与可靠性工程系列: 渲染、缓存与关键路径优化 第一部分:揭秘浏览器 - 关键渲染路径 (CRP) 关键渲染路径 (Critical Rendering Path - CRP) 是指浏览器从接收到最初的 HTML、CSS 和 JavaScript 字节,到最终将它们渲染成可见像素所必须经过的一系列步骤。我们的目标,…

基于CentOS的分布式GitLab+Jenkins+Docker架构:企业级CI/CD流水线实战全记录

引言&#xff1a;从单机到分布式容器架构的演进在传统Web应用部署中&#xff0c;我们常常面临环境不一致、部署效率低下等问题。我曾经维护过一个需要手动在5台服务器上重复部署的游戏项目&#xff0c;每次发布都如同走钢丝。本文将详细分享如何基于CentOS系统&#xff0c;构建…

JVM——为什么Java8移除了永久代(PermGen)并引入了元空间(Metaspace)?

Java8移除永久代并引入元空间&#xff0c;主要是为了解决 PermGen 固定大小、容易导致内存溢出、GC 效率低的问题。元空间使用本地内存&#xff0c;具备更灵活的内存分配能力&#xff0c;提升了垃圾收集和内存管理的效率。 PermGen 的局限性 ①固定大小:永久代的内存空间大小在…

3.正则化——新闻分类

影响结果出了最终的目标&#xff0c;还会有许多细节因素 在机器学习中&#xff0c;往往会面临很多过拟合和欠拟合的问题。 欠拟合是训练不到位&#xff0c;过拟合是训练过头&#xff0c;会导致泛化性差正则化是在损失函数中添加一个惩罚项&#xff0c;以简化模型对于惩罚项Pena…

HTML的重要知识

什么是HTMLHTML是Hyper Text Markup Language的缩写&#xff0c;意思是超文本标记语言。标签标题标签&#xff1a;————-h1,h2,h3.....段落标签 &#xff1a;————p换行标签&#xff1a; ————br列表标签&#xff1a;有序列表&#xff1a;——ol无序列表&#xff1a;—…

【C语言网络编程】HTTP 客户端请求(发送请求报文过程)

在 C 语言中&#xff0c;我们可以使用 socket 编程来手动实现一个简单的 HTTP 客户端&#xff0c;像浏览器一样请求网页数据。本文将结合实际代码&#xff0c;重点讲解如何通过 C 语言构造并发送一个 HTTP 请求报文&#xff0c;实现与服务器的基本通信。 文章目标 通过一个简单…

oracle2kingbase的字段长度问题

实验一&#xff1a; oracle中&#xff1a; create table testlen(c1 varchar2(2)); insert into testlen values(山); --成功 insert into testlen values(山西); --失败 ORA-12899: 列 "TESTK"."TESTLEN"."C1" 的值太大 (实际值: 4, 最大值: 2…

单链表的题目,咕咕咕

1.咕 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 struct ListNode* removeElements(struct ListNode* head, int val) …

关于程序=数据结构+算法这句话最近的一些思考

最近看了很多单片机STM32的的相关程序&#xff0c;尤其是设计到ringbuff、buffer_manage、os_memory预计mem_manage等程序中间层的用法&#xff0c;我对这句话有了一些更深的思考&#xff0c;现在记录下来&#xff0c;希望对处于相同阶段的程序一些思想启迪。首先“数据结构”也…

Rust 错误处理

Rust 错误处理 引言 Rust 是一种系统编程语言&#xff0c;以其安全、并发和性能著称。在 Rust 中&#xff0c;错误处理是一个核心概念&#xff0c;它确保了程序在遇到异常情况时能够优雅地处理。本文将深入探讨 Rust 中的错误处理机制&#xff0c;包括错误类型、错误传播、错误…

17. 什么是 webSocket ?

总结 WebSocket 是 HTML5 引入的一种新协议&#xff0c;允许客户端和服务器之间进行双向实时通信。建立在 TCP 协议之上&#xff0c;默认端口是 80&#xff08;ws&#xff09; 和 443&#xff08;wss&#xff09;&#xff0c;没有同源限制&#xff0c;客户端可以与任意服务器通…

从零开始跑通3DGS教程:(五)3DGS训练

写在前面 本文内容 所属《从零开始跑通3DGS教程》系列文章; 本文介绍在docker中训练3dgs的方法 平台/环境 linux, nvidia GPU, docker 转载请注明出处: https://blog.csdn.net/qq_41102371/article/details/146535874 目录 写在前面系列文章准备docker创建环境参考完系列文章…