ITPub博客

首页 > 数据库 > 数据库开发技术 > Python必知必会:令人相见恨晚的10个Python类技巧(二)

Python必知必会:令人相见恨晚的10个Python类技巧(二)

原创 数据库开发技术 作者:jackzhang 时间:2024-08-30 06:30:00 0 删除 编辑

前路漫漫,我爱Python。Hello,大家好!上一篇文章(Python必知必会:令人相见恨晚的10个Python类技巧(一))我向大家分享了10个关于Python类的关键技巧的前5个,今天我们继续剩下的5个。助你写出更加优雅、高效和Pythonic的代码!

6. 名称改编(Name mangling)

在Python中,name mangling(名称改编)是用来处理类中的私有属性和方法的一种机制。当类的属性或方法的名称以双下划线(__)开头(但不以双下划线结尾)时,Python会自动将其名称改编为一种特定格式。这种机制的目的是为了避免子类意外覆盖父类的私有属性和方法。

class MyClass:
    def __init__(self):
        self.__private_attribute = 42

    def __private_method(self):
        return "This is a private method"

在这个例子中:

  • self.__private_attribute 被改编为 self._MyClass__private_attribute
  • self.__private_method() 被改编为 self._MyClass__private_method()

为什么使用Name Mangling

  • 避免命名冲突: 名称改编机制能够避免在继承链中的子类和父类之间出现名称冲突。因为改编后的名称包含了类名,所以子类无法轻易访问或覆盖父类的私有属性和方法。
  • 保护类内部的实现细节: 尽管Python没有真正的私有属性或方法的概念(因为所有东西都可以通过某种方式访问),名称改编提供了一种约定,让程序员知道某些属性和方法是类的内部实现细节,应该避免直接访问。

一起来看一个实际的例子:

class Parent:
    def __init__(self):
        self.__private_attr: str = "This is private"

    def __private_method(self) -> str:
        return "This is a private method"

class Child(Parent):
    def access_private(self) -> str:
        return self.__private_method()

if __name__ == '__main__':
    p: Parent = Parent()
    c: Child = Child()

    print(p._Parent__private_attr)  # 直接访问被改编的私有属性
    print(p._Parent__private_method())  # 直接调用被改编的私有方法

    # print(c.__private_method())  # 这将会报错,因为 __private_method 已被改编
    print(c._Parent__private_method())  # 正确访问父类的私有方法
This is private
This is a private method
This is a private method

在上面的例子中:

  • Parent 类的 __private_attr 和 __private_method 被改编为 _Parent__private_attr 和 _Parent__private_method。
  • 尽管 Child 类继承了 Parent 类,直接访问 c.__private_method() 会报错,因为该方法已经被改编为 _Parent__private_method。

注意

名称改编并不意味着属性或方法完全是私有的。通过知道类的名称和改编规则,还是可以直接访问这些所谓的“私有”属性和方法。因此,名称改编更多的是一种警告(不要直接访问或修改),而不是一种严格的访问控制机制。

7. @property 装饰器及可管理的属性

在Python中,@property 装饰器用于将类的方法转换为属性。这种机制允许我们定义可管理的属性(managed attributes),即通过方法控制属性的获取、设置和删除操作。这样可以在不改变类的接口的情况下,对属性的访问进行控制和管理。

@property 装饰器

@property 装饰器用于将一个方法转换为一个属性,从而可以像访问普通属性一样访问方法的返回值。它常用于以下几种情况:

  • 延迟计算: 当一个属性的计算非常昂贵,且并非每次访问都需要时,可以通过 @property 来延迟计算,只在需要时才进行。
  • 数据校验: 在设置属性值时,自动进行一些校验。
  • 只读属性: 使得属性可以被读取但不能被修改。

使用 @property 定义可管理的属性

我们可以使用 @property 来定义一个 getter 方法,它使得我们可以像访问普通属性一样访问一个方法的返回值。通常我们会与 @property 搭配 @<property_name>.setter 和 @<property_name>.deleter 一起使用,以定义属性的 setter 和 deleter 方法。

class Rectangle:
    def __init__(self, width: int, height: int) -> None:
        self._width: int = width
        self._height: int = height

    @property
    def width(self) -> int:
        """Getter method for the width attribute."""
        return self._width

    @width.setter
    def width(self, value: int) -> None:
        """Setter method for the width attribute with validation."""
        if value <= 0:
            raise ValueError("Width must be positive.")
        self._width = value

    @property
    def height(self) -> int:
        """Getter method for the height attribute."""
        return self._height

    @height.setter
    def height(self, value: int) -> None:
        """Setter method for the height attribute with validation."""
        if value <= 0:
            raise ValueError("Height must be positive.")
        self._height = value

    @property
    def area(self) -> int:
        """Read-only property that computes the area of the rectangle."""
        return self.width * self.height

if __name__ == '__main__':
    rect: Rectangle = Rectangle(width=10, height=5)
    print(rect.width)  # 输出: 10
    print(rect.height)  # 输出: 5
    print(rect.area)  # 输出: 50

    rect.width = 20
    print(rect.width)  # 输出: 20
    print(rect.area)  # 输出: 100

    # rect.area = 200  # 会报错: AttributeError: property 'area' of 'Rectangle' object has no setter

在这个例子中,我们通过 @property 定义了可管理的属性,提供了一种优雅的方法来控制对类属性的访问和修改,同时保持了简洁的接口。

总之,@property 和可管理的属性在Python中非常有用,可以让我们定义类时隐藏复杂的实现细节,提供一种更清晰和易于使用的接口。通过 @property,我们可以轻松实现属性的延迟计算、数据校验、只读属性等功能,而不需要改变类的外部接口。

8. 使用 type() 定义类

通常情况下,我们都是使用 class 关键字来创建类:

class Dog:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age
        
    def bark(self) -> None:
        print('Woof!')

但是,我们可以使用 type() 方法动态地创建类。其语法如下:

type(name, bases, dict)
  • name 是表示类名称的字符串。
  • bases 是该类继承的父类元组。
  • dict 是包含该类所有方法的字典。

下面我们通过 type() 方法来动态地创建相同的类:

def __init__(self, name: str, age: int) -> None:
    self.name = name
    self.age = age

def bark(self) -> None:
    print('Woof!')

if __name__ == '__main__':
    methods_dict: dict = {'__init__': __init__, 'bark': bark}
    Dog = type('Dog', (), methods_dict)
    dog: Dog = Dog(name='Akita', age=5)
    print(dog.__dict__)     # {'name': 'Akita', 'age': 5}
    dog.bark()  # Woof!

如果我们想要在不使用 class 关键字的前提下动态地创建类,这种方法就非常适用。

9. __slots__ 属性

在Python中,类的 __slots__ 属性允许我们定义在对象上允许的属性,从而实现对属性数量的控制。

在下面的示例中,只能在 Person 对象上定义 name 和 age 属性。如果我们尝试定义这两种属性以外的其他属性,比如,性别(gender),则会发生错误:

class Person:
    __slots__: list[str] = ['name', 'age']

if __name__ == '__main__':
    person = Person()
    person.name = 'Jack'
    person.age = 25
    print(f'Name: {person.name}, Age: {person.age}')    # Name: Jack, Age: 25

    person.gender = 'Male'  # AttributeError: 'Person' object has no attribute 'gender'

如果我们想要严格限制类的属性以确保开发中的结构,那这种方式非常有用。

10. 将类用作装饰器

如果我们在类中定义了 __cal__ 魔术方法,那么类也可以用作装饰器。

当我们像函数一样调用对象时,__cal__ 魔术方法定义了对象的行为。

在下面的示例中,我们定义了一个装饰器类,在被装饰函数的返回值后面添加一个符号(感叹号):

class SymbolAppend:
    def __init__(self, symbol: str):
        self.symbol = symbol

    def __call__(self, func):
        def wrapper(name):
            return func(name) + self.symbol
        return wrapper

@SymbolAppend('!')
def greet(city: str) -> str:
    return f'Welcome to {city}'

if __name__ == '__main__':
    print(greet('Beijing')) # Welcome to Beijing!

在这里:

  • 当 SymbolAppend('!') 发生时,会创建一个 SymbolAppend 对象,其中 symbol = '!'。
  • 在函数上方添加 @append(‘!’) 与 greet =append(‘!’)(greet) 的效果相同。
  • 然后,__call__ magic 方法被调用,并将 greet 重新分配给 wrapper,在返回值后面添加感叹号。
  • 因此,返回 Welcome to Beijing! 。

如果要使用函数作为装饰器实现相同的目的,则会涉及双嵌套:

def symbol_append(symbol: str):
    def decorator(func):
        def warpper(name):
            return func(name) + symbol
        return warpper
    return decorator

@symbol_append("!")
def greet(city: str) -> str:
    return f'Welcome to {city}'

if __name__ == '__main__':
    print(greet('Beijing')) # Welcome to Beijing!

从上面的对比可以看出,使用类作为装饰器可以降低代码的复杂性,同时增强其可读性和简洁性。

总结

好啦,关于Python类的10个技巧就分享到这啦!感谢你的阅读,希望对你有所帮助。


推荐阅读👇👇👇

Python必知必会:令人相见恨晚的10个Python类技巧(一)

5个良好的Python编程习惯,助你成为Python高手!

Python类型提示(type hints):提升代码质量与可读性的利器!墙裂推荐~~

Python编程技巧:在函数参数中请停止使用“x=[]”

9个令人相见恨晚的Python字典技巧,让你写出更优雅、更Pythonic的代码!

- END -

🌟 如果你喜欢这篇文章并且愿意支持我的话,你可以: 🌟

  • 👍👍👍 点赞文章
  • ⤴️⤴️⤴️ 转发文章
  • ⚛️⚛️⚛️ 点击在看
  • 🧑‍🤝‍🧑🧑‍🤝‍🧑🧑‍🤝‍🧑 让文章被推荐给更多人,找到更多志同道合的伙伴
  • 👏👏👏 欢迎给我留言

关注我,第一时间接收更多关于Python、数据分析和机器学习的深入分析和最新趋势。我们一起探索数据的力量,解锁AI的潜能。你的每一次互动都是对我的极大鼓励,也是我们共同成长的动力!

👇👇👇 关注并开启星标提醒,让知识与技能的火花不断在你的职业道路上燃烧吧!

原文作者:数据派探险家;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2017-12-19

  • 博文量
    119
  • 访问量
    55947