前言
最近在看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装饰器是非常有用的装饰器,可以基于方法定义类属性,精确控制属性的读取、赋值和删除行为,灵活实现动态属性等功能。
最后
看一些开源大佬源码也是提升自己很快的方式,会学到一些更便捷更高级的特性。