前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >这几个高级技巧,让 Python 类如虎添翼

这几个高级技巧,让 Python 类如虎添翼

作者头像
数据STUDIO
发布2024-05-23 13:37:42
700
发布2024-05-23 13:37:42
举报
文章被收录于专栏:数据STUDIO数据STUDIO
使用Python类可以创建对象,处理复杂的数据结构、流程、管道、算法或机器学习模型。面向对象编程(OOP)提供了模块化和可重用性,使数据科学家和机器学习工程师能够开发灵活、可扩展的代码库。将代码结构化为类和对象对于回顾性开发工作非常有用,无论是添加新功能、修改现有功能还是修复错误。

在 Python 中,通常有三种类型的方法:实例方法、静态方法和类方法

实例方法是以 self 作为第一个参数定义的方法,它将类的实例作为隐式输入,允许用户与类的属性进行交互。实例方法功能强大,因为它们可以访问和修改实例中的数据和配置,从而执行复杂的计算和实现复杂的逻辑,并具有很高的可读性和可维护性。

静态方法使用 @staticmethod 装饰器定义,属于类而不是类的实例,不能通过 self 访问实例或其属性。这些方法通常用于在特定类的上下文中定义实用功能。

最后,还有类方法,它们与类绑定,而不是与类的实例绑定,它们可以修改类的状态,使其适用于所有实例。我们将着重讨论“类方法”及其为我们的代码增添额外 OOP 优势潜能。我将分享一些专门针对数据科学和机器学习应用的技巧,希望你能将它们应用到你的日常工作流程中。

什么是类方法?

一个实用的例子就是创建单例类。单例类是一种设计模式,这里你可以限制一个类只能有一个实例。下面是一个实现:

代码语言:javascript
复制
class Singleton(object):
    """The famous Singleton class that can only have one instance."""

    _instance = None

    def __new__(cls, *args, **kwargs):
        """Create a new instance of the class if one does not already exist."""
        if cls._instance is not None:
            raise Exception("Singleton class can only have one instance.")
        cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance
        
# 创建第一个实例
single = Singleton()

# 创建第二个实例
double = Singleton()

# Error Output:
# Exception: Singleton class can only have one instance.

在这里,当尝试实例化 double 时,代码会失败,因为它通过检查类属性 _instance 的状态,检测到 Singleton 的实例已经存在。我们可以通过检查该属性来明确了解这一情况:

代码语言:javascript
复制
Singleton._instance
# Output:
# <__main__.Singleton at 0x7f7c10491f30>

但如何改变整个 Singleton 类的状态的呢?

__new__ 方法的定义中,第一个参数是 cls,代表类对象。这意味着 __new__ 是一个类方法,可以改变整个类的状态,而典型的实例方法只能改变类中特定实例的状态。因此,当我们创建第一个实例(隐式调用 __new__)时,我们可以改变类本身的一些基本特性,表明我们已经使用过它一次。

“类方法”背后的整个理念是允许在类中定义与类本身而非其实例绑定的方法,从而允许修改类的行为,使其更加灵活。

在数据科学和机器学习中,这种灵活性非常宝贵。类方法为管理数据处理、模型配置或数据库连接的类的实例化提供了更有效的替代方法,最终会带来更简洁、可维护、可扩展的代码。

这里有一些实际用例,这些用例证明了@classmethods 是特别有用的。

类方法与类本身绑定,而不是与类的实例绑定。它们可以改变类的状态,使其适用于类的所有当前或未来实例。

如何在数据项目中使用类方法

1. 数据处理器的替代构造函数

数据处理类是数据相关项目和管道中最典型的类。想象一下,你有一个名为 "DataProcessor "的类,它负责处理一些复杂的数据处理任务列表。通常,通过使用内存中的数据对其进行初始化,然后对其进行处理来创建该类的实例。

如下所示

代码语言:javascript
复制
class DataProcessor:
    def __init__(self, data):
        self.data = data  # take data in from memory

    def process_data(self):
        # complicated code to process data in memory
        ...

# Using the class with initial data in memory
processor = DataProcessor(data=data)
processor.process_data()

想象一下,你想让这个类更灵活,可以从磁盘读取 csv 文件。如果简单地添加一个读取文件的方法,类的实例化过程就会出现问题。你需要用空数据对象来实例化类,然后运行数据加载方法来覆盖这些数据。

代码语言:javascript
复制
class DataProcessor:
    def __init__(self, data):
        self.data = data  # take data in from memory

    def process_data(self):
        # complicated code to process data in memory
        ...
    
    def from_csv(self, filepath):
        self.data = pd.read_csv(filepath)

# Using the class without initial data in memory
processor = DataProcessor(data=None)
processor.from_csv("path_to_your_file.csv")
processor.process_data()

这种方法有效但效率低、冗长,而且确实难看。

另一种更好的方法是使用 @classmethods,定义一个类方法 from_csv() 作为替代构造函数。它接受替代输入(例如 filepath 而不是内存中的 data),使得我们可以直接从 CSV 文件加载数据创建 DataProcessor 实例。外观如下

代码语言:javascript
复制
class DataProcessor:
    def __init__(self, data):
        self.data = data

    def process_data(self):
        ...

    @classmethod
    def from_csv(cls, filepath):
        data = pd.read_csv(filepath)
        return cls(data)

# Instantiating and using the class with classmethod
processor = DataProcessor.from_csv("path_to_your_file.csv")
processor.process_data()

如何通过.from_csv()使类的实例化更简洁高效?就好像有了一个进入类的秘密窗口一样,你需要决定通过门还是窗来获取数据,取决于你的使用情况。(默认情况下,类是在内存中获取数据,还是从文件路径中获取数据)。

当然,这种替代类构造函数的概念还可以扩展。例如,如果要从 parquet 文件加载数据,可以为此添加另一个类方法。

代码语言:javascript
复制
class DataProcessor:
    def __init__(self, data):
        self.data = data

    def process_data(self):
        ...

    @classmethod
    def from_csv(cls, filepath):
        ...

    @classmethod
    def from_parquet(cls, filepath):
        data = pd.read_parquet(filepath)
        return cls(data)

此外我们还可以定义一个 from_file 工厂方法,它可以检测传入的文件类型,并调用相应的加载器。

2. 模型封装器的替代构造函数

替代构造函数的概念可以很容易地扩展到ML 模型封装器。假设你有一个名为 MyXGBModel 的模型类,它是 XGBoost 库的封装器。它接收一些参数,初始化一个模型,并可能处理一些训练、评估和其他常规建模任务。

下面是它的基本版本,没有对 XGBoost 模型行为进行任何有趣的更改。

代码语言:javascript
复制
import xgboost

class MyXGBModel:
    def __init__(self, learning_rate=0.1, n_estimators=100, max_depth=3):
        self.learning_rate = learning_rate
        self.n_estimators = n_estimators
        self.max_depth = max_depth
        self.model = self._create_model()

    def _create_model(self):
        model = xgboost.XGBClassifier(learning_rate=self.learning_rate,
                                      n_estimators=self.n_estimators,
                                      max_depth=self.max_depth)
        return model

# Usage example
model = MyXGBModel(learning_rate=0.05, n_estimators=200, max_depth=5)

要初始化这个模型,通常需要像上面的例子一样传递一堆参数。或者,我们也可以像下面这样在参数字典中传递这些变量:

代码语言:javascript
复制
# Initializing with a dictionary of parameters
params = {'learning_rate': 0.05, 'n_estimators': 200, 'max_depth': 5}
model_from_dict = MyXGBModel(**params)

将这些参数放在 JSON 配置文件中而不是内存中的字典里,这样该怎么办呢?一种方法是加载 JSON 文件并创建参数,然后将其传递给模型。

代码语言:javascript
复制
import json

# Load parameters from a json config file
with open('config.json', 'r') as file:
    params = json.load(file)
  
# the contents of params is the same  
# params = {'learning_rate': 0.05, 'n_estimators': 200, 'max_depth': 5}

# Now initializing with a dictionary of parameters
model_from_dict = MyXGBModel(**params)

同样,它有点难看,而且过于冗长。更糟糕的是,它依赖于类外的代码块。因此,定义一个类方法来简化和改进这一过程。

代码语言:javascript
复制
class MyXGBModel:
    def __init__(self, learning_rate=0.1, n_estimators=100, max_depth=3):
        ...

    def _create_model(self):
        ...

    @classmethod
    def from_config_file(cls, file_path):
        import json
        with open(file_path, 'r') as file:
            config = json.load(file)
        return cls(**config)

# Usage
model_from_config = MyXGBModel.from_config_file('config.json')

这样看起来是不是更漂亮了?

使用类方法,我们可以一次性从文件中获取所有参数。另一种构造函数直接使用配置文件中的参数,省去了类外的任何模板代码。新的实现方式更简洁、直接、可维护性更高,也更容易为其他开发人员所理解。

可以进一步利用这一技术,创建方便的方法来加载预配置模型,从而简化模型初始化过程。在下面我们将介绍如何实现这一点。

3. 预配置模型

为了便针对特定场景预定义设置,从配置文件初始化模型的概念可以通用化,快速初始化模型。比如,我们想定义一个用于快速迭代的 quick_start 模型,以及一个用于更复杂任务的 high_accuracy 模型。可以使用类方法来提供这些预配置选项。

代码语言:javascript
复制
class MyXGBoostModel:
    def __init__(self, learning_rate=0.1, n_estimators=100, max_depth=3):
        ...

    def _create_model(self):
        ...

    @classmethod
    def from_config_file(cls, file_path):
        ...

    @classmethod
    def quick_start(cls):
        default_params = {'learning_rate': 0.05, 'n_estimators': 100, 'max_depth': 4}
        return cls(**default_params)

    @classmethod
    def high_accuracy(cls):
        high_acc_params = {'learning_rate': 0.01, 'n_estimators': 500, 'max_depth': 10}
        return cls(**high_acc_params)

# Usage
quick_start_model = MyXGBoostModel.quick_start()
high_accuracy_model = MyXGBoostModel.high_accuracy()

类方法可以优雅地为模型的预配置设置提供初始化。通过定义每个预配置设置的特定类方法,我们可以快速调用它们,而无需手动指定每个参数。这样做可以大大减少模板代码的数量,使代码更简洁、易读和易维护。

类似地,类方法的功能与数码相机的预设配置(如横向、纵向、夜间模式等)非常相似。虽然可以手动设置光圈和快门速度来进行自定义拍摄,但预设配置可以限制这些设置,以便适合特定使用情况。这些选项为我们的建模探索提供了起点,并且在实践中,我们可以进行适当的网格搜索和超参数调整,以找到预测模型的最佳参数集。

4. 数据库连接器的开发配置与生产配置

来看看类方法的另一个实际用例:创建一个数据库连接器类。需要指出,我不是数据库/开发运营工程师。介绍的代码可能不是建立数据库连接的最安全方法。如果你打算在工作中使用这种设计模式,需要咨询你的工程团队,需要符合他们的标准。

接下来看下如何利用类方法创建一个典型的数据库连接器,并为开发(dev)和生产(prod)环境预定义配置。

设置和管理数据库连接器在处理多个环境时总是很棘手,原因在于每个环境通常都有自己独特的设置。在不同环境之间切换,尤其是在笔记本电脑中,可能会导致许多错误和不一致性。此外,如果要处理敏感数据,出于安全考虑,管理数据库凭据需要格外小心。

一种优雅的解决方案是为每种模式(开发与生产)定义单独的类方法,利用环境变量以独特的设置启动连接器。这不仅提供了一致性,还增强了代码的可读性和健壮性。

为了说明这一点,让我们定义一个"DatabaseConnector"类,其中包含用于在开发和生产环境中设置连接的类方法。

代码语言:javascript
复制
from getpass import getpass

class DatabaseConnector:
    def __init__(self, account, user, password):
        self.account = account
        self.user = user
        self._password = getpass("enter the DB Password:")  # Keep password private
        self._connection = None

    @classmethod
    def development_config(cls):
        return cls(
            account=os.environ.get("DEV_DB_ACCOUNT"),
            user=os.environ.get("DEV_DB_USER"),
            password=os.environ.get("DEV_DB_PASSWORD") or getpass("Enter Dev DB Password: ")
        )

    @classmethod
    def production_config(cls):
        return cls(
            account=os.environ.get("PROD_DB_ACCOUNT"),
            user=os.environ.get("PROD_DB_USER"),
            password=os.environ.get("PROD_DB_PASSWORD") or getpass("Enter Prod DB Password: ")
        )

    @property
    def params(self):
        # Exclude password from the public parameters
        return {
            "account": self.account,
            "user": self.user
        }

    def initialize_connection(self):
        if not self._connection:
     # define create_database_connection based on your specific database (e.g. postgres, snowflake, redshift, etc)
            self._connection = create_database_connection(self.account, self.user, self._password)

    def query_data(self, query):
        if not self._connection:
            raise Exception("Database connection not initialized")
        return execute_query(self._connection, query)

使用这个超酷的数据库连接器,只需像这样使用不同的类方法实例化这个类:

代码语言:javascript
复制
# Dev Database
dev_db_conn = DatabaseConnector.development_config()
dev_db_conn.initialize_connection()
dev_data = dev_db_conn.query_data("SELECT * FROM dev_table_name")

# Prod Database
prod_db_conn = DatabaseConnector.production_config()
prod_db_conn.initialize_connection()
prod_data = prod_db_connector.query_data("SELECT * FROM prod_table_name")

在运行这些行之前,你唯一需要做的是将数据库用户名和密码作为环境变量存储一次。剩下的就交给这个类来处理。

我们将该类方法用作在开发环境和产品环境之间切换的开关,而无需重新配置整个数据库连接。这样可以避免编写访问数据所需的所有模板代码,节省更多时间。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-05-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据STUDIO 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是类方法?
  • 如何在数据项目中使用类方法
    • 1. 数据处理器的替代构造函数
      • 2. 模型封装器的替代构造函数
        • 3. 预配置模型
          • 4. 数据库连接器的开发配置与生产配置
          相关产品与服务
          数据库
          云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档