面向对象属性与特性

前言

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

最后

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

相关推荐
Falling4212 分钟前
使用 CNB 构建并部署maven项目
后端
阿蒙Amon19 分钟前
【Python小工具】使用 OpenCV 获取视频时长的详细指南
python·opencv·音视频
程序员小假22 分钟前
我们来讲一讲 ConcurrentHashMap
后端
爱上语文30 分钟前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端
萧曵 丶39 分钟前
Rust 中的返回类型
开发语言·后端·rust
橘子编程1 小时前
Python-Word文档、PPT、PDF以及Pillow处理图像详解
开发语言·python
蓝婷儿1 小时前
Python 机器学习核心入门与实战进阶 Day 2 - KNN(K-近邻算法)分类实战与调参
python·机器学习·近邻算法
高兴达2 小时前
Spring boot入门工程
java·spring boot·后端
之歆2 小时前
Python-封装和解构-set及操作-字典及操作-解析式生成器-内建函数迭代器-学习笔记
笔记·python·学习