您当前的位置:首页 > 计算机 > 编程开发 > Python

Python Magic Method 与 Setup 方法:深入解析与应用

时间:09-23来源:作者:点击数:
城东书院 www.cdsy.xyz

Python 中的魔法方法

Python 中的魔法方法,也称为双下划线方法或特殊方法,格式为 “方法名”。这些方法无需主动调用,而是在特定场景下由 Python 解释器自动调用。魔法方法的作用是自定义类的行为,以便与内置操作符(例如 +、-、*、/、== 等)和函数(例如 len()str() 等)交互。

魔法方法在不同的场景下被调用,例如:

  • __init__:在创建类的实例时初始化属性,是对象实例化时最常见的被调用的方法之一。
  • __str__:当对象转换为字符串时被调用,如使用 str(obj) 转换为字符串或 print(obj) 面向用户输出对象等场景。目的是面向终端用户,提供用户友好的对象的字符串表示内容,不一定是对象完整精确描述,只对终端用户负责。如果类没有定义 __str__ 方法,Python 会试图把调用 __repr__ 的字符串结果返回给用户。
  • __repr__:定义对象完整的、精确的、机器可读的字符串表示形式,如在 repr(obj) 方法调用,IDLE、Jupyter Notebook 等交互式环境中直接输入对象名时的输出内容等场景。面向 Python 内部,是对象合法的表达式字符串,通过该表达式字符串,可通过 eval() 函数执行并重新生成原始对象,即 eval(repr(obj)) == obj 应该为 True。如果类只定义了 __str__ 而没有定义 __repr__ 方法,Python 会试图调用 __str__ 方法来代替,但这通常不是一个好的做法。
  • __add__:定义对象的加法行为,如两个自定义对象相加时被调用。
  • __eq__:定义对象的相等性比较行为,用于判断两个对象是否相等。
  • __len__:定义对象的长度,常用于自定义容器类,当使用 len() 函数对对象进行操作时被调用。

这些魔法方法只是 Python 中的一部分,还有其他用于自定义对象行为的魔法方法,具体用法取决于我们的需求。使用魔法方法可以使我们的自定义类更具 Pythonic 和可读性。

在这里插入图片描述

二、常见 Magic Method 详解

(一)构造与初始化

__new__ 和 __init__ 在对象创建过程中起着至关重要的作用。__init__ 是我们很熟悉的初始化方法,在对象初始化的时候调用,通常被理解为 “构造函数”。实际上,当我们调用 x = SomeClass() 的时候,__init__ 并不是第一个执行的,__new__ 才是。

__new__ 是用来创建类并返回这个类的实例,而 __init__ 只是将传入的参数来初始化该实例。__new__ 在创建一个实例的过程中必定会被调用,但 __init__ 就不一定,比如通过 pickle.load 的方式反序列化一个实例时就不会调用 __init____new__ 方法总是需要返回该类的一个实例,而 __init__ 不能返回除了 None 的任何值。

例如:

class Foo(object):
    def __init__(self):
        print('foo __init__')
        return None  # 必须返回 None,否则抛 TypeError
    def __del__(self):
        print('foo __del__')

在对象的生命周期结束时,__del__ 会被调用,可以将 __del__ 理解为 “析构函数”。__del__ 定义的是当一个对象进行垃圾回收时候的行为。有一点容易被人误解,实际上,x.__del__() 并不是对于 del x 的实现,但是往往执行 del x 时会调用 x.__del__()

(二)字符串表示

__str__ 和 __repr__ 在对象字符串表示中有不同的用途。__str__ 是一个对象的非正式的、易于阅读的字符串描述,当类 str 实例化(str(object))时会被调用,以及会被内置函数 format() 和 print() 调用。目的是面向终端用户,提供用户友好的对象的字符串表示内容,不一定是对象完整精确描述,只对终端用户负责。如果类没有定义 __str__ 方法,Python 会试图把调用 __repr__ 的字符串结果返回给用户。

例如:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __str__(self):
        return f"Dog named {self.name} who is {self.age} years old"
my_dog = Dog("Buddy", 3)
print(my_dog)

__repr__ 是一个对象的官方的字符串描述,会被内置函数 repr() 方法调用,它的描述必须是信息丰富的和明确的。面向 Python 内部,是对象合法的表达式字符串,通过该表达式字符串,可通过 eval() 函数执行并重新生成原始对象,即 eval(repr(obj)) == obj 应该为 True。如果类只定义了 __str__ 而没有定义 __repr__ 方法,Python 会试图调用 __str__ 方法来代替,但这通常不是一个好的做法。

例如:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __repr__(self):
        return f"Dog(name='{self.name}', age={self.age})"
my_dog = Dog("Buddy", 3)
print(repr(my_dog))
(三)属性操作

__getattr____setattr____delattr__ 方法对对象属性的获取、设置和删除操作起着重要作用。

  • __getattr__(self, name) 定义了当用户试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向,或者对一些废弃的属性进行警告。
  • __setattr__(self, name, value) 是实现封装的解决方案,它定义了你对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,它都允许你为该属性进行赋值,因此你可以为属性的值进行自定义操作。但要注意实现 __setattr__ 时要避免 “无限递归” 的错误,正确的写法应该是 self.__dict__[name] = value
  • __delattr__(self, name) 与 __setattr__ 很像,只是它定义的是你删除属性时的行为。实现 __delattr__ 是同时要避免 “无限递归” 的错误。

例如:

class Access(object):
    def __getattr__(self, name):
        print('__getattr__')
        return super(Access, self).__getattr__(name)
    def __setattr__(self, name, value):
        print('__setattr__')
        return super(Access, self).__setattr__(name, value)
    def __delattr__(self, name):
        print('__delattr__')
        return super(Access, self).__delattr__(name)
access = Access()
access.attr1 = True  # __setattr__调用
access.attr1  # 属性存在,只有__getattribute__调用
try:
    access.attr2  # 属性不存在,先调用__getattribute__,后调用__getattr__
except AttributeError:
    pass
del access.attr1  # __delattr__调用
(四)其他重要方法

__getattribute____call____len__ 等方法在不同场景下有着重要的应用。

  • __getattribute__(self, name) 定义了你的属性被访问时的行为,相比较,__getattr__ 只有该属性不存在时才会起作用。因此,在支持 __getattribute__ 的 Python 版本,调用 __getattr__ 前必定会调用 __getattribute____getattribute__ 同样要避免 “无限递归” 的错误。需要提醒的是,最好不要尝试去实现 __getattribute__,因为很少见到这种做法,而且很容易出 bug。
  • __call__ 在类中,允许创建可调用的对象 (实例)。就是说可以让函数(方法)可以像对象一样被调用。以 “对象名 ()” 的形式使用。
  • __len__ 定义当被 len() 调用时的行为(返回容器中元素的个数)。

例如:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __call__(self, *args, **kwargs):
        return "<Point {}:{}>".format(self.x, self.y)
p = Point(4, 5)
print(p())

class MyList:
    def __init__(self, values=None):
        self.values = values or []
    def __len__(self):
        return len(self.values)
my_list = MyList([1, 2, 3])
print(len(my_list))

三、Setup 方法的作用与用法

(一)基础用法

在 Python 中,setup 方法主要用于构建和安装软件包。它提供了一种简单而灵活的方式来定义软件包的元数据、依赖关系等。

setup 方法通常位于 setup.py 文件中,通过使用 setuptools 模块的 setup 函数来实现。以下是一个简单的 setup.py 文件示例:

from setuptools import setup

setup(
    name='mypackage',
    version='1.0',
    author='Your Name',
    author_email='your@email.com',
    description='A brief description of my package',
    packages=['mypackage'],
    install_requires=['dependency1', 'dependency2'],
)

在这个示例中,name 参数指定了包的名称,version 参数指定了包的版本号,author 和 author_email 参数指定了包的作者信息,description 参数提供了包的简要描述。packages 参数指定了要安装的包的名称,这里假设 mypackage 是一个包含 __init__.py 文件的 Python 包。install_requires 参数列出了包的依赖关系,当安装这个包时,这些依赖项会自动被安装。

例如,如果我们的软件包依赖于 numpy 和 matplotlib,可以这样指定:

setup(
   ...
    install_requires=['numpy', 'matplotlib'],
)

这样,在安装我们的软件包时,如果用户的环境中没有安装 numpy 和 matplotlib,它们将会被自动安装。

(二)扩展用法

除了基本用法外,setup 方法还提供了一些参数以满足更复杂的需求。

  • entry_points 参数:这个参数允许我们在安装软件包时创建可执行脚本或命令行工具。我们可以指定一个字典,其中键是工具的名称,值是要运行的函数或脚本。安装软件包后,这些工具将自动添加到系统的可执行路径中。

例如,要创建一个名为 mytool 的工具,可以在 setup 函数中添加以下代码:

setup(
   ...
    entry_points={'console_scripts': ['mytool=mypackage.tool:main',]},
)

这将创建一个名为 mytool 的可执行文件,执行 mypackage.tool 模块中的 main 函数。

  • data_files 参数:这个参数允许我们将其他非 Python 文件包含在软件包中,例如配置文件、模板文件等。我们可以指定一个列表,其中每个元素表示一个文件或目录。安装软件包时,这些文件将被复制到指定的目标路径中。

例如,要将一个名为 config.ini 的配置文件包含在软件包中,可以在 setup 函数中添加以下代码:

setup(
   ...
    data_files=[('config', ['config.ini']),],
)

这将把 config.ini 文件复制到软件包安装路径下的 config 目录中。

(三)安装方式

使用 setup 方法打包的软件包可以通过多种方式进行安装。

  • pip 安装:这是最常见的安装方式。确保你已经安装了 pip。然后,在命令行中进入软件包的根目录,并执行以下命令:
    pip install .
    
    这将根据 setup.py 文件中的配置信息安装软件包及其依赖库。
    如果应用在开发过程中会频繁变更,每次安装还需要先将原来的版本卸掉,很麻烦。使用 “develop” 开发方式安装的话,应用代码不会真的被拷贝到本地 Python 环境的 “site-packages” 目录下,而是在 “site-packages” 目录里创建一个指向当前应用位置的链接。这样如果当前位置的源码被改动,就会马上反映到 “site-packages” 里。
    开发方式安装命令如下:
    pip install -e .
    
  • yum 安装(并非标准方式):通常情况下,Python 软件包不使用 yum 进行安装。但在某些特定的环境中,如果有特殊的需求,可以通过创建自定义的 yum 仓库或者使用其他工具将 Python 软件包集成到 yum 管理中。但这种方式比较复杂,且不是主流的安装方式。

总之,setup 方法在 Python 软件包的构建和安装中起着重要的作用,通过合理使用其各种参数和安装方式,可以方便地管理和分发 Python 代码。

四、Magic Method 与 Setup 方法的关系

Magic Method 和 Setup 方法在 Python 编程中虽然有着不同的功能,但也存在一些关联和相互作用。

Magic Method 主要用于自定义类的行为,使得对象能够与内置操作符和函数进行交互,从而实现更加灵活和强大的编程。例如,通过定义 __str__ 和 __repr__ 方法,可以控制对象的字符串表示形式,方便在调试和输出时提供更有意义的信息。通过定义 __add____eq__ 等方法,可以实现自定义对象的运算和比较操作。

Setup 方法则主要用于构建和安装软件包,定义软件包的元数据、依赖关系以及安装方式等。它为开发者提供了一种方便的方式来组织和分发代码。

在某些情况下,Magic Method 可以与 Setup 方法结合使用。例如,在构建一个自定义的数据结构类时,可以使用 Magic Method 来定义该类的特殊行为,如 __len__ 方法用于返回数据结构的长度。如果这个数据结构类被打包成一个软件包,那么 Setup 方法可以用来安装这个软件包,并确保其依赖关系得到满足。

另外,当使用 Setup 方法创建可执行脚本或命令行工具时,可以利用 Magic Method 来定义这些工具的行为。例如,可以定义 __call__ 方法,使得工具对象可以像函数一样被调用。

总之,Magic Method 和 Setup 方法在 Python 编程中各有其独特的作用,但它们也可以相互结合,为开发者提供更全面的编程解决方案。了解它们之间的关联和相互作用,可以帮助开发者更好地利用 Python 的强大功能,提高开发效率。

城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐