Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the wp-pagenavi domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/blog.zhujinhui.net/wp-includes/functions.php on line 6114

Notice: 函数 _load_textdomain_just_in_time 的调用方法不正确twentyseventeen 域的翻译加载触发过早。这通常表示插件或主题中的某些代码运行过早。翻译应在 init 操作或之后加载。 请查阅调试 WordPress来获取更多信息。 (这个消息是在 6.7.0 版本添加的。) in /var/www/blog.zhujinhui.net/wp-includes/functions.php on line 6114
学习计划 – 第 5 页 – 煅魂-JeffreyChu的修炼屋

Py面试必问:说说魔术方法与应用

在中高级Python开发面试中,面试官为了考察面试者是否了解面向对象编程,会通过Python魔术方法相关概念来变向提问

引言

定义和基本概念

魔术方法(Magic Methods) 是 Python 中一种特殊的函数,它们用于定义类的特定行为和属性。这些方法通常由 Python 解释器自动调用,而不是显式地在代码中调用。

它们允许你自定义类与内置操作符、函数和语法结构的交互方式,使得自定义对象能够像内置类型一样自然地使用。

命名约定

魔术方法的命名有特定的格式要求,它们都以双下划线__开头和结尾。这种命名约定使得这些方法在类中具有特殊的用途,不会与普通的方法名发生冲突。例如:

学习原因
  • 提升代码可读性和可维护性
  • 实现类的特殊行为和高级功能

常见魔术方法

初始化、删除、调用
  • __init__(self, ...):对象初始化方法(构造函数)
  • __new__(cls, ...):创建对象实例的方法,通常与__init__ 搭配使用
  • __del__(self):对象销毁方法(析构函数)
  • __call__(self, ...):对象被调用时,比如xxx()这样调用形式,就会调用__call__方法的内容
字符串表示
  • __str__(self):定义对象的字符串表示,适用于 str()print()
  • __repr__(self):定义对象的官方字符串表示,适用于 repr(),通常可通过 eval() 函数重建对象
  • __format__(self, format_spec):定义对象的格式化表示,适用于 format() 函数和格式化字符串
  • __bytes__(self):定义对象的字节串表示,适用于 bytes()
数值运算
  • __neg__(self):一元负号(取负),即 -self
  • __pos__(self):一元正号,即 +self
  • __abs__(self):绝对值,即 abs(self)
  • __invert__(self):按位取反,即 ~self
  • __add__(self, other):加法,即 self + other
  • __sub__(self, other):减法,即 self - other
  • __mul__(self, other):乘法,即 self * other
  • __matmul__(self, other):矩阵乘法,即 self @ other
  • __truediv__(self, other):真除法,即 self / other
  • __floordiv__(self, other):取整除法,即 self // other
  • __mod__(self, other):取模,即 self % other
  • __divmod__(self, other):同时计算取整除法和取模,返回 (self // other, self % other)
  • __pow__(self, other, modulo=None):幂运算,即 self ** other
  • __lshift__(self, other):左移位运算,即 self << other
  • __rshift__(self, other):右移位运算,即 self >> other
  • __and__(self, other):按位与,即 self & other
  • __xor__(self, other):按位异或,即 self ^ other
  • __or__(self, other):按位或,即 self | other
  • __radd__(self, other):反向加法,即 other + self
  • __rsub__(self, other):反向减法,即 other - self
  • __rmul__(self, other):反向乘法,即 other * self
  • __rmatmul__(self, other):反向矩阵乘法,即 other @ self
  • __rtruediv__(self, other):反向真除法,即 other / self
  • __rfloordiv__(self, other):反向取整除法,即 other // self
  • __rmod__(self, other):反向取模,即 other % self
  • __rdivmod__(self, other):反向同时计算取整除法和取模,返回 (other // self, other % self)
  • __rpow__(self, other, modulo=None):反向幂运算,即 other ** self
  • __rlshift__(self, other):反向左移位运算,即 other << self
  • __rrshift__(self, other):反向右移位运算,即 other >> self
  • __rand__(self, other):反向按位与,即 other & self
  • __rxor__(self, other):反向按位异或,即 other ^ self
  • __ror__(self, other):反向按位或,即 other | self
  • __iadd__(self, other):增量加法赋值,即 self += other
  • __isub__(self, other):增量减法赋值,即 self -= other
  • __imul__(self, other):增量乘法赋值,即 self *= other
  • __imatmul__(self, other):增量矩阵乘法赋值,即 self @= other
  • __itruediv__(self, other):增量真除法赋值,即 self /= other
  • __ifloordiv__(self, other):增量取整除法赋值,即 self //= other
  • __imod__(self, other):增量取模赋值,即 self %= other
  • __ipow__(self, other, modulo=None):增量幂运算赋值,即 self **= other
  • __ilshift__(self, other):增量左移位赋值,即 self <<= other
  • __irshift__(self, other):增量右移位赋值,即 self >>= other
  • __iand__(self, other):增量按位与赋值,即 self &= other
  • __ixor__(self, other):增量按位异或赋值,即 self ^= other
  • __ior__(self, other):增量按位或赋值,即 self |= other
类型转换
  • __int__(self):定义对象到整数的转换,适用于 int()
  • __float__(self):定义对象到浮点数的转换,适用于 float()
  • __complex__(self):定义对象到复数的转换,适用于 complex()
  • __bool__(self):定义对象的布尔值转换,适用于 bool()
  • __index__(self):定义对象作为索引的转换,适用于切片操作和 bin()hex()oct() 函数
集合操作
  • __len__(self):定义对象的长度,适用于 len()
  • __getitem__(self, key):定义按键访问,适用于 obj[key]
  • __setitem__(self, key, value):定义按键赋值,适用于 obj[key] = value
  • __delitem__(self, key):定义按键删除,适用于 del obj[key]
  • __contains__(self, item):定义成员测试,适用于 item in obj
迭代协议
  • __iter__(self):定义返回迭代器,适用于 iter()
  • __next__(self):定义返回下一个元素,适用于 next()
上下文管理
  • __enter__(self):定义进入上下文管理时的行为,适用于 with 语句
  • __exit__(self, exc_type, exc_value, traceback):定义退出上下文管理时的行为,适用于 with 语句
属性访问
  • __getattr__(self, name):定义访问不存在的属性时的行为
  • __getattribute__(self, name):定义访问任何属性时的行为
  • __setattr__(self, name, value):定义设置属性时的行为
  • __delattr__(self, name):定义删除属性时的行为
描述符协议
  • __get__(self, instance, owner):定义描述符的获取行为
  • __set__(self, instance, value):定义描述符的设置行为
  • __delete__(self, instance):定义描述符的删除行为
比较操作
  • __lt__(self, other):小于,适用于 <
  • __le__(self, other):小于等于,适用于 <=
  • __eq__(self, other):等于,适用于 ==
  • __ne__(self, other):不等于,适用于 !=
  • __gt__(self, other):大于,适用于 >
  • __ge__(self, other):大于等于,适用于 >=

常见应用示例

字符串表示

__str__ 方法用于定义对象的字符串表示,通常用于用户友好的输出,而 __repr__ 方法用于定义对象的官方字符串表示,通常可以用来重建对象。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

point = Point(3, 4)
print(str(point))  # 输出:(3, 4)
print(repr(point))  # 输出:Point(3, 4)
排序和比较

通过实现一系列比较运算的魔术方法,可以定义自定义类的排序和比较行为。

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

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

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

    def __repr__(self):
        return f"Student({self.name}, {self.score})"

students = [
    Student("Alice", 85),
    Student("Bob", 75),
    Student("Charlie", 90)
]
# 根据分数排序
sorted_students = sorted(students)
print(sorted_students)

# 输出:[Student(Bob, 75), Student(Alice, 85), Student(Charlie, 90)]

在这个例子中,通过实现 __lt__ 方法,定义了对象的小于比较。因此可以使用 sorted() 函数对学生列表进行排序。

实现数值类

通过重载算术运算符的魔术方法,可以实现自定义数值类,如复数或矩阵。

class ComplexNumber:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag

    def __add__(self, other):
        return ComplexNumber(
            self.real + other.real,
            self.imag + other.imag
        )

    def __mul__(self, other):
        return ComplexNumber(
            self.real * other.real - self.imag * other.imag,
            self.real * other.imag + self.imag * other.real
        )

    def __repr__(self):
        return f"{self.real} + {self.imag}i"

# 创建两个复数
c1 = ComplexNumber(2, 3)
c2 = ComplexNumber(4, 5)

# 复数加法
print(c1 + c2)  # 输出:6 + 8i

# 复数乘法
print(c1 * c2)  # 输出:-7 + 22i

在这个例子中,通过重载 __add____mul__ 方法,实现了复数的加法和乘法

集合与序列协议

通过实现集合与序列协议相关的魔术方法,可以定义自定义容器类,使其支持索引和迭代。

比如我们要实现一个既能排序又能去重的SortedSet类:

class SortedSet:
    def __init__(self, items=None):
        self._items = sorted(set(items)) if items else []

    def __repr__(self):
        return f"SortedSet({self._items})"

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

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

    def __iter__(self):
        return iter(self._items)

    def __getitem__(self, index):
        return self._items[index]

    def add(self, item):
        if item not in self._items:
            self._items.append(item)
            self._items.sort()

    def remove(self, item):
        if item in self._items:
            self._items.remove(item)

# 测试 SortedSet 类
s = SortedSet([3, 1, 2, 3, 4])
print(s)           # 输出:SortedSet([1, 2, 3, 4])
print(len(s))      # 输出:4
print(2 in s)      # 输出:True
print(s[0])        # 输出:1
s.add(5)
print(s)           # 输出:SortedSet([1, 2, 3, 4, 5])
s.remove(2)
print(s)           # 输出:SortedSet([1, 3, 4, 5])
属性访问控制

通过实现属性访问相关的魔术方法,可以实现动态属性和代理模式。

class DynamicAttribute:
    def __getattr__(self, name):
        if name == "age":
            return 25
        elif name == "name":
            return "Alice"
        else:
            raise AttributeError(
                f"'DynamicAttribute' object has no attribute '{name}'"
            )

obj = DynamicAttribute()
print(obj.age)  # 输出:25
print(obj.name)  # 输出:Alice

在这个例子中,通过实现 __getattr__ 方法,动态地为对象添加属性。如果属性不存在,则会调用这个方法来获取属性值,这在Python一些的ORM框架中很常见。

构造查询语句

python中的运算符|&其实可以对标SQL中的orand,所以我们可以通过魔术方法来实现一些类似SQL语句拼接

# 注:代码块暂不考虑太多异常情况,比如not操作
class Q:
    def __init__(self, *expressions, operator="and", **kwargs):
        self.operator = operator
        self._expressions = expressions
        self._kwargs = kwargs

    def __or__(self, other):
        return Q(self, other, operator="or")

    def __str__(self):
        return self.translate()

    def translate(self):
        kv_sql = " and ".join([f"{k}={v}" for k, v in self._kwargs.items()])
        expr_sql = f" {self.operator} ".join([expr.translate() for expr in self._expressions])
        return expr_sql + (f" {self.operator} " + kv_sql if expr_sql and kv_sql else kv_sql)


# 示例用法
q1 = Q(name='Alice', height=168)
q2 = Q(age=25, gender=0)
q3 = Q(q1, age=25)

# 实现 OR 运算符
print(q1 | q2)
# 输出:name=Alice and height=168 or age=25 and gender=0
print(q3)
# 输出:name=Alice and height=168 and age=25

上面例子中kwargs参数代表的是参与and操作的拼接键值对参数,而|则表示两个对象要进行or操作的拼接,这样就可以实现类似ORM框架里的还原SQL字符串 拼接操作了

上下文管理

上下文管理是 Python 中用于管理资源的一种机制,它可以确保在进入和离开代码块时资源得到正确的管理和释放。在使用 with 语句时,Python 会调用对象的 __enter__ 方法来获取上下文管理器,并在离开代码块时调用 __exit__ 方法来释放资源。

class MyResource:
    def __enter__(self):
        print("Entering the resource")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exiting the resource")

    def operation(self):
        print("Performing operation")

# 使用上下文管理器
with MyResource() as resource:
    resource.operation()

# Outputs
# Entering the resource
# Performing operation
# Exiting the resource

在这个示例中,MyResource 类实现了上下文管理器。当进入 with 代码块时,Python 会调用 __enter__ 方法,然后执行其中的代码;当离开 with 代码块时,Python 会调用 __exit__ 方法,以确保资源被正确释放。

可调用对象

__call__ 方法使得一个对象可以像函数一样被调用,这对于实现可调用对象非常有用。当调用对象时,Python 解释器会自动调用对象的 __call__ 方法。

class MyCallable:
    def __call__(self, x):
        return x * 2

# 创建可调用对象
callable_obj = MyCallable()

# 调用可调用对象
result = callable_obj(5)
print(result)  # 输出:10

在这个示例中,MyCallable 类实现了 __call__ 方法,使得它的实例可以像函数一样被调用。当调用 callable_obj(5) 时,Python 实际上调用了 callable_obj.__call__(5)

__new____init__

__new__ 方法和 __init__ 方法都是 Python 中用于创建类实例的特殊方法,但它们的作用略有不同。

__new__ 方法在实例创建之前调用,用于创建实例对象,并返回一个新创建的实例。
__init__ 方法在实例创建之后调用,用于初始化实例对象的属性。

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("Creating instance")
        instance = super().__new__(cls)
        return instance

    def __init__(self, x):
        print("Initializing instance")
        self.x = x

# 创建实例
obj = MyClass(5)

# Outputs
# Creating instance
# Initializing instance

在这个示例中,__new__ 方法在实例创建之前被调用,用于创建实例对象,而 __init__ 方法在实例创建之后被调用,用于初始化实例对象的属性。

总结

Python 魔术方法是一种特殊的命名约定,用双下划线包围,用于实现对象的特殊行为和操作。它们包括但不限于初始化、字符串表示、比较、算术运算、上下文管理等方面。通过合理使用魔术方法,可以提高代码的可读性、可维护性,并实现更灵活的编程逻辑,是面向对象编程里一种非常重要的实现方法。

你见过这么“癫”的二维码吗?

日常生活,我们见过的二维码中大多数都是由白底和很多黑点组成的二维条码,以X秘书表白墙的链接 为例:

http://love520.zhujinhui.net/?a=Jeffrey&b=X%E7%A7%98%E4%B9%A6&d=2024-01-01

它对应的普通二维码是这样的:

由于它是X秘书网站下的链接,想让它带有X秘书的元素符号:

还能给它点颜色瞧瞧,让它看起来像个真正的logo

既然是个表白墙链接,心动的特效总要有吧:

当然还可以更“癫”点,生成一个“贪吃蛇”的二维码:

反正一个二维码有多“癫”,全看“艺术家”的代码艺术。

言归正传,这些二维码图片是到底是怎么生成的呢?这其实是python的一个很有趣的第三方库——amzqr实现的,GitHub地址为:

https://github.com/x-hw/amazing-qr

简介

amzqr是一个二维码生成器,它帮助我们可生成普通二维码带图片的艺术二维码(黑白与彩色)动态二维码(黑白与彩色)

安装

pip install amzqr

使用方法

它有两种使用方式,一个是命令行,一种在python中直接调用库

命令行方式

提示:如果你尚未安装 amzqr ,以下内容请使用python(3) amzqr.py 而非amzqr 。)

amzqr  Words    # 二维码扫描解析出来的字符串,可以是url也可以是普通文字
      [-v {1,2,3,...,40}]   # 二维码边长,1~40
      [-l {L,M,Q,H}]  # 纠错等级L、M、Q、H,依次升高,默认H
      [-n output-filename]  # 生成的二维码图片名称,格式可以是 .jpg, .png ,.bmp ,.gif 
      [-d output-directory]  # 生成的二维码图片所在目录,默认当前目录
      [-p picture_file]  # 底图,二维码生成后与这个指定的底图融合,可以是静态图片,也可以是gif,默认为空
      [-c]  # 是否涂色,不传则不
      [-con contrast]  # 用来调节图片的对比度,1.0 表示原始图片,更小的值表示更低对比度,更大反之。默认为1.0
      [-bri brightness]  # 用来调节图片的亮度,其余用法和取值与 -con 相同。

比如 上面的“X秘书表白墙LOGO版二维码.png”的生成例子:

amzqr  "http://love520.zhujinhui.net/?a=Jeffrey&b=X%E7%A7%98%E4%B9%A6&d=2024-01-01"  -p xmishu.jpg -n xmishu_color_qrcode.png -c
作为导入文件
# 安装模块后
from amzqr import amzqr
version, level, qr_name = amzqr.run(
    words,  # 二维码扫描解析出来的字符串,可以是url也可以是普通文字
    version=1,  # 二维码边长,1~40
    level='H',  # 纠错等级L、M、Q、H,依次升高,默认H
    picture=None,  # 底图,二维码生成后与这个指定的底图融合,可以是静态图片,也可以是gif,默认为空
    colorized=False,  # 是否涂色
    contrast=1.0, # 用来调节图片的对比度,1.0 表示原始图片,更小的值表示更低对比度,更大反之。默认为1.0
    brightness=1.0,  # 用来调节图片的亮度,其余用法和取值与 -con 相同。
    save_name=None,  # 生成的二维码图片名称,格式可以是 .jpg, .png ,.bmp ,.gif 
    save_dir=os.getcwd()  # 生成的二维码图片所在目录,默认当前目录
)

最近520也是快到了,那么你可以找来一个动漫亲吻的gif,或者用你们真人拍摄视频后转过来的gif也可以,做picture底图来生成代表爱情的二维码:

生成一个有亲吻画面的二维码:

from amzqr import amzqr

amzqr.run(
    "http://love520.zhujinhui.net/?a=Jeffrey&b=X%E7%A7%98%E4%B9%A6&d=2024-01-01",
    picture="kiss.gif",
    save_name="kiss_qrcode.gif",
    colorized=True,
)

生成效果:

注意事项

  • 请尽可能采用正方形或近似正方形的图片,否则可能会被压缩导致比例失衡,因为二维码最终都是正方形图片
  • 建议在图片尺寸大的时候使用 -v 的值也应该适当变大。
  • 尽可能少用有透明无色部分的图片

今天你学会了吗?学会了赶紧去试试吧!欢迎大家交流讨论!