面向对象属性与特性

前言

最近在看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装饰器是非常有用的装饰器,可以基于方法定义类属性,精确控制属性的读取、赋值和删除行为,灵活实现动态属性等功能。

最后

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

相关推荐
aqi004 分钟前
15天学会AI应用开发(七)有了大模型为什么还要引入RAG
人工智能·python·大模型·ai编程·ai应用
长栎1 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode1 小时前
Redis 在生产项目的使用
前端·后端
用户559822481221 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode1 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战1 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha1 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn1 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425911 小时前
ShardingJDBC
后端
行者全栈架构师1 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端