面向对象属性与特性

前言

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

最后

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

相关推荐
Open-AI10 分钟前
Python如何判断一个数是几位数
python
极客代码13 分钟前
【Python TensorFlow】入门到精通
开发语言·人工智能·python·深度学习·tensorflow
义小深16 分钟前
TensorFlow|咖啡豆识别
人工智能·python·tensorflow
疯一样的码农20 分钟前
Python 正则表达式(RegEx)
开发语言·python·正则表达式
代码之光_198021 分钟前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
ajsbxi26 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
颜淡慕潇1 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
进击的六角龙1 小时前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
一只爱好编程的程序猿2 小时前
Java后台生成指定路径下创建指定名称的文件
java·python·数据下载
Aniay_ivy2 小时前
深入探索 Java 8 Stream 流:高效操作与应用场景
java·开发语言·python