面向对象属性与特性

前言

最近在看python源码时,看到有很多地方使用@property装饰器,激发了我的兴趣,决定再次探索一下面向对象属性

私有属性

熟悉Java的朋友都知道,可以使用关键字public、private等来表达是否私有。那Python是如何做的呢?

在Python中所有的类属性和方法是默认公开的,不过可以通过增加__双下划线来表示私有,像这样

ruby 复制代码
class Foo:
    def __init__(self):
        self.__a = 'a'

这样,我们创建实例访问这个属性就会报错

scss 复制代码
foo = Foo()
foo.__a  # AttributeError: 'Foo' object has no attribute '__a'

虽然这样访问不了,但换个方式就可以访问,像这样

ini 复制代码
foo = Foo()
foo._Foo__a  # a

当使用__{var}的方式定义私有属性时,Python解释器只是重新给了它一个别名:_{class}__{var},所以用这个别名还是可以访问的。所以,这其实就是定义了一个不容易被子类重写的属性。

但我们看一些开源代码时,往往只会加一个下划线。因为这其实就是一个"君子协议"。

实例内容都在字典中

一个类实例的所有成员,都保存在__dict__的字典属性中

ruby 复制代码
class Foo:
    def __init__(self):
        self.__a = 'a'
​
foo = Foo()
foo.__dict__  # {'_Foo__a': 'a'}

不光实例有字典,类也有

sql 复制代码
Foo.__dict__  # {'__module__': '__main__', '__init__': <function Foo.__init__ at 0x10e60f670>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
​

总结一下:

  • 实例的__dict__中,保存着当前实例的所有数据
  • 类的__dict__中,保存着类的文档、方法等所有数据

案例:将某个字典中的数据直接赋值到实例上

方法一:利用setattr

ini 复制代码
d = {'name': 'zhangsan', 'age': 18}
for k, v in d.items():
    setattr(foo, k, v)

方法二:利用__dict__

arduino 复制代码
d = {'name': 'zhangsan', 'age': 18}
foo.__dict__.update(d)
foo.__dict__  # {'_Foo__a': 'a', 'name': 'zhangsan', 'age': 18}

注意:虽然这两种方法都可以实现,但还是有区别的,类的属性设置行为可以通过定义__setattr__魔法方法来修改

ruby 复制代码
class Foo:
    def __init__(self):
        self.__a = 'a'
​
    def __setattr__(self, key, value):
        if key == 'name':
            raise ValueError(f'Invalid key: {key}')
        
        super().__setattr__(key, value)
​
foo = Foo()
​
d = {'name': 'zhangsan', 'age': 18}
for k, v in d.items():
    setattr(foo, k, v)
​
foo.__dict__  # ValueError: Invalid key: name

但使用__dict__可以绕开这个限制

css 复制代码
class Foo:
    def __init__(self):
        self.__a = 'a'
​
    def __setattr__(self, key, value):
        if key == 'name':
            raise ValueError(f'Invalid key: {key}')
​
        super().__setattr__(key, value)
​
foo = Foo()
​
d = {'name': 'zhangsan', 'age': 18}
foo.__dict__.update(d)
​
foo.__dict__  # {'_Foo__a': 'a', 'name': 'zhangsan', 'age': 18}

类方法装饰器

用类方法还是静态方法?

  • 发现某个行为不属于实例,而是属于整个类型,考虑使用类方法
  • 某个方法不需要使用实例里的任何内容,可以考虑静态方法

用静态方法还是普通方法?

  • 静态方法通用,与类关系不大,可以改为普通函数
  • 静态方法与类关系密切,使用静态方法
  • 静态方法有先天优势,比如可以被自类继承、重写

属性装饰器

使用@property装饰器来将一个方法变为属性调用。

假设我们有一个类Person,其中有一个birthyear属性和一个计算年龄的方法:

ruby 复制代码
class Person:
    def __init__(self, birthyear):
        self._birthyear = birthyear
​
    def age(self):
        return 2024 - self._birthyear

我们希望以person.age的形式而不是person.age()来获取年龄。

此时,我们就需要使用@property装饰器,将age()方法变为一个属性:

ruby 复制代码
class Person:
    def __init__(self, birthyear):
        self._birthyear = birthyear
​
    @property
    def age(self):
        return 2024 - self._birthyear
      
person = Person(1990)
print(person.age)  # 输出:34

如果你想为该属性添加一个setter方法,你可以使用@attribute.setter装饰器:

ruby 复制代码
class Person:
    def __init__(self, name):
        self._name = name
​
    @property
    def name(self):
        return self._name
​
    @name.setter
    def name(self, value):
        if isinstance(value, str):
            self._name = value
        else:
            raise ValueError("Name must be a string.")
            
person = Person("John")
person.name = "Jack"  # 设置姓名
print(person.name)  # 输出:Jack

@property装饰器是非常有用的装饰器,可以基于方法定义类属性,精确控制属性的读取、赋值和删除行为,灵活实现动态属性等功能。

最后

看一些开源大佬源码也是提升自己很快的方式,会学到一些更便捷更高级的特性。

相关推荐
阡之尘埃1 分钟前
Python数据分析案例59——基于图神经网络的反欺诈交易检测(GCN,GAT,GIN)
python·神经网络·数据挖掘·数据分析·图神经网络·反欺诈·风控大数据
Lossya2 分钟前
【自动化测试】常见的自动化遍历工具以及如何选择合适的自动化遍历工具
自动化测试·功能测试·测试工具·自动化·测试
xiaojiesec5 分钟前
第157天: 安全开发-Python 自动化挖掘项目&SRC 目标&FOFA 资产&Web 爬虫解析库
python·安全
27划流星雨_9 分钟前
from tqdm.auto import tqdm用法详细介绍
python
掐指一算乀缺钱10 分钟前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
爱里承欢。14 分钟前
【Python语言初识(二)】
python
hzw051021 分钟前
Jupyter的使用
ide·python·jupyter
憨憨小白1 小时前
函数的高级应用
开发语言·python·青少年编程·少儿编程
CV-King2 小时前
计算机视觉硬件知识点整理(三):镜头
图像处理·人工智能·python·opencv·计算机视觉
惟长堤一痕2 小时前
医学数据分析实训 项目三 关联规则分析作业--在线购物车分析--痹症方剂用药规律分析
python·数据分析