31天Python入门——第20天:魔法方法详解

|----------------|
| 你好,我是安然无虞。 |

文章目录

    • 魔法方法
      • [1. `new__和__del`](#1. __new__和__del__)
      • [2. `repr__和__len`](#2. __repr__和__len__)
      • [3. `enter__和__exit`](#3. __enter__和__exit__)
      • [4. 可迭代对象和迭代器](#4. 可迭代对象和迭代器)
      • [5. 中括号`[]`数据操作](#5. 中括号[]数据操作)
      • [6. `getattr`、`setattr` 和 `delattr`](#6. __getattr____setattr____delattr__)
      • [7. 可调用的](#7. 可调用的)
      • [8. 运算符](#8. 运算符)

魔法方法

魔法方法: Python中的魔法方法是一类特殊的方法, 它们以双下划线 __ 开头和结尾, 用于实现类的特殊行为.这些魔法方法在Python中具有特殊的含义, 可以让你自定义类的行为, 使其更符合你的需求, 它们一般是自动调用, 也可以通过内置函数来显式调用.

1. __new__和__del__

__new__ 是 Python 中的一个特殊魔法方法, 用于创建对象实例.它在对象实例被创建之前被调用, 通常用于控制对象的创建过程和自定义对象的创建方式.与之相对的是 __init__ 方法, 它在对象实例创建后进行初始化操作.

__new__的注意点:

  1. 返回实例对象: __new__ 方法必须返回一个类的实例对象.通常情况下, 它会调用父类的 __new__ 方法来创建实例, 并返回实例对象.
  2. 第一个参数是类: __new__ 方法的第一个参数是类本身, 通常命名为 cls.在调用时, Python 会自动传递类作为第一个参数.
  3. 控制实例创建过程: __new__ 方法允许你控制实例的创建过程.你可以在这个方法中实现自定义的逻辑.
python 复制代码
class MyClass:
	# 在__init__之前被调用
    def __new__(cls, name):
        print("__new__")
        obj = super().__new__(cls)
        return obj

    def __init__(self, name):
        print("__init__")
        self.name = name


my_class = MyClass('张三')
print(my_class.name)
        

# __new__
# __init__
# 张三
python 复制代码
# __new__方法可以控制实例创建过程:
# 单例模式 - 无论你实例化多少次, 始终都是同一个对象

class Singleton:

    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls)
        return obj

    def __init__(self):
        pass


single1 = Singleton()
print(id(single1))
single2 = Singleton()
print(id(single2))

# 140613070163824
# 140613070163392

# 很明显上面并没有实现单例模式, 那怎么实现单例模式呢?
class Singleton:

    __instance = None

    def __new__(cls, *args, **kwargs):
        # 假如已经实例化过了, 就直接返回已经存在的对象
        if not cls.__instance:
            cls.__instance = super().__new__(cls)

        return cls.__instance

    def __init__(self):
        pass


single1 = Singleton()
print(id(single1))
single2 = Singleton()
print(id(single2))

# 140240883584880
# 140240883584880

__del__当对象被释放的时候, 会执行. 它通常用于执行一些资源释放或清理操作, 如关闭文件、释放网络连接等. 但是, 但并不保证一定会调用, 因为垃圾回收可能由不同的策略来管理. python中对象的释放是一个比较复杂的过程. 一个对象有可能在引用到0的时候被释放, 且这个释放是可能在任意一个地方发生.

注意:

__del__跟python中的关键字del是没有关系的.del并不一定会触发__del__

python 复制代码
class TestDel:

    def __init__(self):
        print("__init__")

    def __del__(self):
        print("__del__")


test_del = TestDel()
print()

Python当中的垃圾回收可以查看网上比较优秀的文章.

2. __repr__和__len__

__repr__ 用于定义对象的字符串表示形式.

__str____repr__都返回一个对象的字符串描述, 不过两者的用途不同, str可以理解是给人阅读的, 而repr是给程序使用的.

print(obj)str(obj)方法调用对象的str方法;交互式CLI、repr(obj)、gdb调试时查看对象值返回的是repr, 不过在多情况下程序员把str和repr设置为一样__str__ == __repr__.

如果没有定义 __str__ 方法, 调用 print() 时会默认使用 __repr__ 方法.

__len__: 用于定义对象的长度(大小).当你在一个对象上调用内置的 len() 函数时, Python 会查找该对象是否定义了 __len__ 方法, 如果有则调用该方法并返回长度值.

python 复制代码
class MyList:

    def __init__(self, items):
        self.items = items

    def __len__(self):
        return len(self.items)


my_list = MyList('hello python')
print(len(my_list))

3. __enter__和__exit__

上下文协议是一组特殊方法(__enter__和__exit__同时存在), 允许你创建可用于 with 语句的上下文管理器.使用上下文管理器可以在进入和退出代码块时执行特定操作, 如资源的分配和释放.Python 的上下文协议涉及两个主要的特殊方法:__enter____exit__.

python 复制代码
    __enter__(self)

__enter__ 方法:

  • 在进入with语句代码块前被调用, 用于执行一些准备操作.
  • 可以返回一个值, 该值将被赋给 as 关键字之后的变量.
python 复制代码
    __exit__(self, exc_type, exc_value, exc_traceback)

__exit__ 方法:

  • 在退出with语句代码块时调用, 无论代码块是否发生异常.
  • 可以用于执行一些清理操作, 如资源的释放.
  • 如果代码块内没有异常发生, 参数为 None, None, None.如果有异常, 参数包含异常类型、异常实例和跟踪信息.
  • 如果 __exit__ 方法返回 True, 异常不会向上继续传播.返回 False 则异常会继续向上抛.

上下文协议的使用案例:可以使用 with 语句来管理文件的打开和关闭, 确保文件资源在退出代码块时被正确释放.

python 复制代码
class FileHandler:
    # 一个实现了上下文协议的类
    def __init__(self):
        pass

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass
    

file_handler = FileHandler()
with file_handler:
    print(11111111111)
python 复制代码
logger = logging.getLogger(__name__)

class FileHandler:
    # 一个实现了上下文协议的类
    def __init__(self, file_name, mode, encoding='utf-8'):
        self.file_name = file_name
        self.mode = mode
        self.encoding = encoding
        self.file = None

    def __enter__(self):
        self.file = open(self.file_name, self.mode, encoding=self.encoding)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            logger.error(f"出错: {exc_val}")

        if self.file:
            print("文件正在关闭")
            self.file.close()
        return True


file_handler = FileHandler("test.py", "r")
with file_handler as f:
    content = f.read()
    print(content)

# 上下文协议的实现简化了异常捕获的代码, 让异常捕获的封装性更好
try:
  file = open()
except Exception:
  # 处理这个异常
  pass
finally:
 	file.close()

4. 可迭代对象和迭代器

可迭代对象和迭代器是 Python 中用于遍历数据集合的概念.它们在很多情况下都用于实现数据的迭代操作, 如循环.

可迭代对象(Iterable)

可迭代对象是一种数据类型, 可以通过迭代获取其中的元素.

在 Python 中, 任何实现了 __iter__() 方法的对象都被视为可迭代对象.常见的可迭代对象包括列表、元组、字典、集合、字符串等.

可迭代对象的特点:

  • 可以使用 for 循环遍历元素.
  • 可以通过 iter() 函数将其转换为迭代器.
python 复制代码
class MyNumber:

    def __init__(self, numbers):
        self.numbers = numbers

    def __iter__(self):
        # __iter__()必须返回迭代器
        return iter(self.numbers)


a_list = [1,2,3,4,5,6,7]
my_numbers = MyNumber(a_list)

print(isinstance(my_numbers, Iterable)) # True

for num in my_numbers:
    print(num)
    
    
# 如果没有实现__iter__()就会报错:
class MyNumber:

    def __init__(self, numbers):
        self.numbers = numbers

a_list = [1,2,3,4,5,6,7]
my_numbers = MyNumber(a_list)

print(isinstance(my_numbers, Iterable)) False

for num in my_numbers:
    print(num)

迭代器Iterator

迭代器是一个实现了 __iter__()__next__() 方法的对象.__iter__() 方法返回迭代器对象自身, 而 __next__() 方法返回迭代器的下一个元素, 如果没有元素了, 抛出 StopIteration 异常.

迭代器的特点:

  • 可以使用 for 循环遍历元素, 也可以使用 next() 函数逐个获取元素.
  • 只能遍历一次, 遍历完成后就不能再次使用.

总结:

  1. 可迭代对象是具有迭代性质的对象, 而迭代器是一个实现了迭代协议的对象.
  2. 可迭代对象可以通过 iter() 函数转换为迭代器.
  3. 迭代器是一种一次性的数据获取方式, 每次调用 next() 都会获取下一个元素.
python 复制代码
class MyNumbersIterator:

    def __init__(self):
        pass

    def __iter__(self):
        pass

    def __next__(self):
        pass


my_numbers_iterator = MyNumbersIterator()
print(isinstance(my_numbers_iterator, Iterable)) # True
print(isinstance(my_numbers_iterator, Iterator)) # True


# ----------------------------------------------------------------

class MyNumbersIterator:

    def __init__(self, numbers):
        self.numbers = numbers
        self.index = 0

    def __iter__(self):
        # __iter__()必须返回迭代器
        return self

    def __next__(self):
        try:
            num = self.numbers[self.index]
            self.index += 1
        except IndexError:
            raise StopIteration
        return num


a_list = [1,2,3,4,5,6,7]
my_numbers_iterator = MyNumbersIterator(a_list)
print(isinstance(my_numbers_iterator, Iterable))
print(isinstance(my_numbers_iterator, Iterator))

5. 中括号[]数据操作

__getitem____setitem____delitem__ 用于自定义对象的索引访问和操作.它们在创建自定义的容器类时非常有用, 允许你实现类似字典或列表的行为.

  1. __getitem__(self, key)

    这个方法定义了使用索引访问对象时的行为.当你像 obj[key] 这样使用索引操作时, Python 会调用对象的 __getitem__ 方法, 并将索引 key 作为参数传递给这个方法.你可以在这个方法中实现对应的行为, 比如从内部数据结构中获取相应的值.

  2. __setitem__(self, key, value)

    这个方法定义了使用索引赋值操作时的行为.当你像 obj[key] = value 这样进行索引赋值操作时, Python 会调用对象的 __setitem__ 方法, 并将索引 key 和值 value 作为参数传递给这个方法.你可以在这个方法中实现对应的赋值行为.

  3. __delitem__(self, key)

    这个方法定义了使用 del obj[key] 进行索引删除操作时的行为.当你使用 del 删除索引时, Python 会调用对象的 __delitem__ 方法, 并将索引 key 作为参数传递给这个方法.你可以在这个方法中实现删除操作的逻辑.

这些方法的使用可以使你的自定义类更具像内置容器类型(如字典和列表)的行为, 从而实现更灵活和符合预期的数据操作.

python 复制代码
# 补充知识:
# 列表切片 返回的是  列表.
# 元组切片 返回是是  元组.
# print(my_numbers[1:3]) # 切片my_numbers[1:3]相当于slice(1,3,None)
# s = slice(1, 3, None)

from collections.abc import Iterable


class MyNumbers:
    # 全部都是数字

    def __init__(self, numbers):
        self.numbers = numbers

    def __getitem__(self, item):
        if isinstance(item, slice):
            # 如果进了判断, 就代表是在做切片, 则需要返回与原本数据类型一致的数据类型.
            temp = self.numbers[item]
            cls = type(self)
            obj = cls(temp)
            return obj
        else:
            num = self.numbers[item]
            return num

    def __setitem__(self, key, value):
        if isinstance(value, (int, float)):
            self.numbers[key] = value
        else:
            raise ValueError(f'{value}的值设置必须是数字类型.')

    def __str__(self):
        return f"{self.numbers}"

    def __len__(self):
        return len(self.numbers)

    def __contains__(self, item):
        return item in self.numbers

    def __delitem__(self, key):
        # 不允许为空, 起码要有一个元素.
        if len(self.numbers) > 1:
            self.numbers.pop(key)
        else:
            raise ValueError('序列起码要有一个元素')


a_list = [1, 2, 3, 4, 5, 6]
# a_dict = {'a': 1, 'b': 2, 'c': 3}
my_numbers = MyNumbers(a_list)
# my_numbers[0] = 8
del my_numbers[0]
del my_numbers[1]
del my_numbers[2]
del my_numbers[0]
del my_numbers[0]
del my_numbers[0]
print(my_numbers)
# print(2 in my_numbers)
# print(type(my_numbers))
# s = slice(1, 3, None)
# print(type(a_list[s]))
# 列表切片 返回的是  列表.
# 元组切片 返回是是  元组.
# print(my_numbers[1:3])
# print(isinstance(my_numbers, Iterable))
#
# for num in my_numbers:
#     print(num)

6. __getattr____setattr____delattr__

__getattr____setattr____delattr__ 是 Python 中的魔法方法, 用于自定义对象的属性访问和操作.它们允许你控制属性的获取、设置和删除行为, 从而实现自定义的属性操作逻辑.

  1. __getattr__(self, name)

    当你访问一个不存在的属性时, Python 会调用对象的 __getattr__ 方法, 并将属性名 name 作为参数传递给这个方法.你可以在这个方法中实现对应的行为, 比如返回一个默认值或者抛出异常.

  2. __setattr__(self, name, value)

    当你设置属性的值时, Python 会调用对象的 __setattr__ 方法, 并将属性名 name 和值 value 作为参数传递给这个方法.你可以在这个方法中实现对应的赋值行为, 比如进行值的验证或修改.

  3. __delattr__(self, name)

    当你删除属性时, Python 会调用对象的 __delattr__ 方法, 并将属性名 name 作为参数传递给这个方法.你可以在这个方法中实现删除属性的逻辑.

这些方法的使用可以使你的自定义类的属性操作更具控制性和灵活性.

python 复制代码
class Person:

    def __init__(self, name, age, info):
        self.name = name
        self.age = age
        self.info = info

    def __getattr__(self, item):
        if item in self.info:
            return self.info[item]
        else:
            raise AttributeError(f'{self.__class__}没有{item}属性')

    def __setattr__(self, key, value):
        # print(f'__setattr__接收到的参数key的值是{key}, value的值是{value}')
        if key == 'age':
            if 0 < value < 150:
                super().__setattr__(key, value)
            else:
                raise AttributeError(f"{key}的值{value}设置不合法")
        super().__setattr__(key, value)

    def __delattr__(self, item):
        if item in ['name', 'age']:
            raise AttributeError(f"{item!r}是基础属性, 不允许删除")
        super(Person, self).__delattr__(item)


information = {
    'gender': "男",
    'height': 180,
    'hobby': "打篮球",
}
person = Person('张三', 20, information)
person.age = 25
del person.age
print(person.name)
print(person.age)
print(person.gender)
# print(person.height)
# print(person.hobby)
# print(person.aaa)

7. 可调用的

不仅 Python 函数是真正的可调用的, 任何 Python 对象都可以表现得像函数.为此, 只需实现魔法方法 call.

__call__ 是 Python 中的一个特殊魔法方法, 用于使对象可以像函数一样被调用.当一个对象被调用时, Python 会检查对象是否定义了 __call__ 方法, 如果定义了, 就会调用该方法, 从而实现对象的可调用行为.

这使得你可以将一个类的实例像函数一样使用, 提供更多的灵活性和自定义操作.

python 复制代码
from collections.abc import Callable

class Add:

    def __call__(self, a, b):
        return a + b


# def func():
#     print(111)


# add = Add()
# res = add(1, 2)
# print(res)
# print(isinstance(func, Callable))
# print(isinstance(test_call, Callable))

# 类装饰器.

# 创建一个装饰器函数,使被装饰的函数只能在特定的时间段内执行。
# 在早上9点 -> 晚上6点之间, 可以调用, 其余时间不允许调用.
import datetime


def time_limited(start, end):

    def outer(func):
        def inner(*args, **kwargs):
            now = datetime.datetime.now().time()
            if start < now < end:
                result = func(*args, **kwargs)
                return result
            else:
                print(f"对不起, 该时间段无法调用此接口.")
        return inner
    return outer


class TimeLimit:

    def __init__(self, start, end):
        self.start = start
        self.end = end

    def __call__(self, f):

        def inner(*args, **kwargs):
            now = datetime.datetime.now().time()
            if self.start < now < self.end:
                result = f(*args, **kwargs)
                return result
            else:
                print(f"对不起, 该时间段无法调用此接口.")
        return inner


# @time_limited(start=datetime.time(9, 0), end=datetime.time(18, 0))
@TimeLimit(start=datetime.time(9, 0), end=datetime.time(18, 0))
def ceshi():
    print('我只能在特定的时间段内执行.')


if __name__ == '__main__':
    ceshi()

8. 运算符

运算符魔法方法是用于实现对象之间的比较和关系运算的.它们允许你定义自定义的比较操作, 使你的对象可以像内置类型一样进行大小比较、相等性检查等操作.

下面是一些常用的运算符类型的魔法方法及其解释:

  • __lt__(self, other): 小于运算符 < 的魔法方法.定义对象小于另一个对象时的行为.
  • __le__(self, other): 小于等于运算符 <= 的魔法方法.定义对象小于等于另一个对象时的行为.
  • __eq__(self, other): 等于运算符 == 的魔法方法.定义对象相等时的行为.
  • __ne__(self, other): 不等于运算符 != 的魔法方法.定义对象不等于另一个对象时的行为.
  • __gt__(self, other): 大于运算符 > 的魔法方法.定义对象大于另一个对象时的行为.
  • __ge__(self, other): 大于等于运算符 >= 的魔法方法.定义对象大于等于另一个对象时的行为.
python 复制代码
import random


class IntNumber:

    def __init__(self):
        self.value = random.randint(1, 10000)

    def __str__(self):
        return f"{self.value}"

    def __gt__(self, other):
        return self.value > other.value

    def __eq__(self, other):
        return self.value == other.value

    def __add__(self, other):
        return self.value + other.value


class FloatNumber:

    def __init__(self):
        self.value = random.uniform(1, 10000)

    def __str__(self):
        return f"{self.value}"

    def __lt__(self, other):
        return self.value < other.value


int_number = IntNumber()
int_number2 = IntNumber()
print(int_number + int_number2)
# float_number = FloatNumber()
# print(int_number)
# print(int_number2)
# print(float_number)
# print(int_number2 != int_number)
# print(int_number is float_number)


class Date:

    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __gt__(self, other):
        if self.year > other.year:
            return True
        if self.year == other.year:
            if self.month > other.month:
                return True
            elif self.month == other.month:
                return self.day > other.day
            else:
                return False
        if self.year < other.year:
            return False

    def __eq__(self, other):
        return self.year == other.year and self.month == other.month and self.day == other.day


# date1 = Date(2022, 7, 15)
# date2 = Date(2022, 7, 15)
#
# print(date1 == date2)

|----------------------|
| 遇见安然遇见你,不负代码不负卿。 |
| 谢谢老铁的时间,咱们下篇再见~ |

相关推荐
Apifox3 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
掘金一周10 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
The Future is mine30 分钟前
Python计算经纬度两点之间距离
开发语言·python
Enti7c31 分钟前
HTML5和CSS3的一些特性
开发语言·css3
uhakadotcom32 分钟前
构建高效自动翻译工作流:技术与实践
后端·面试·github
九月镇灵将32 分钟前
GitPython库快速应用入门
git·python·gitpython
Asthenia041238 分钟前
深入分析Java中的AQS:从应用到原理的思维链条
后端
爱吃巧克力的程序媛38 分钟前
在 Qt 创建项目时,Qt Quick Application (Compat) 和 Qt Quick Application
开发语言·qt
Asthenia04121 小时前
如何设计实现一个定时任务执行器 - SpringBoot环境下的最佳实践
后端
兔子的洋葱圈1 小时前
【django】1-2 django项目的请求处理流程(详细)
后端·python·django