likes
comments
collection
share

面向对象属性与特性

作者站长头像
站长
· 阅读数 31

前言

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

私有属性

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

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

class Foo:
    def __init__(self):
        self.__a = 'a'

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

foo = Foo()
foo.__a  # AttributeError: 'Foo' object has no attribute '__a'

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

foo = Foo()
foo._Foo__a  # a

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

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

实例内容都在字典中

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

class Foo:
    def __init__(self):
        self.__a = 'a'

foo = Foo()
foo.__dict__  # {'_Foo__a': 'a'}

不光实例有字典,类也有

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

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

方法二:利用__dict__

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

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

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__可以绕开这个限制

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属性和一个计算年龄的方法:

class Person:
    def __init__(self, birthyear):
        self._birthyear = birthyear

    def age(self):
        return 2024 - self._birthyear

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

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

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装饰器:

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

最后

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

转载自:https://juejin.cn/post/7337615321249988617
评论
请登录