python的属性自省

私有属性

Python 中的私有属性(private attribute)通常指以下划线开头的变量或方法,如 _name_method()。这种命名约定并不是强制性的,只是一种惯例,用于提示该属性或方法应该只在类内部使用,而不应该在外部直接访问。

具体来说,Python 中的私有属性具有如下特点:

  1. 私有属性不能被外部直接访问,但可以通过类内部的方法间接地访问。

  2. 在类内部定义的方法可以访问所有属性,包括私有属性,因为它们都在同一个作用域内。

  3. 子类无法继承父类的私有属性,但可以通过公有方法来访问父类的私有属性。

需要注意的是,虽然 Python 中没有真正的私有属性,但可以通过双下划线开头的命名方式(如 __name)来实现名称重整(name mangling)功能,即将属性名转换为 _ClassName__name 的形式,使其难以在外部被访问。不过这种方式仍然存在安全漏洞和易读性问题,因此不建议滥用。

总之,Python 的私有属性机制主要是基于命名约定的,其作用是限制属性的可见性和访问性,从而提高代码的封装性和安全性。但需要注意的是,这种机制只是一种建议性的规范,并不能完全避免私有属性被访问的可能。

"""
直接访问 _classname__variable_name 中的名称等方法来访问和修改私有属性
"""


class MyClass:
    one_attar = 'abc'
    _two_attr = 100
    # python中私有并没有实现真正的私有,只是在保存属性的时候改了个名字,在外部无法直接访问
    __three_attr= 300  # 私有属性
    __four_attr__ = 400 # 不推荐做法


class Person:
    def __init__(self, name_, age_):
        self.__name = name_
        self.__age = age_

    def get_name(self):
        return self.__name

    def get_age(self):
        return self.__age

    def set_name(self, name_):
        self.__name = name_

    def set_age(self, age_):
        self.__age = age_


if __name__ == '__main__':
    obj1 = MyClass()
    print(obj1.one_attar)
    print(obj1._two_attr)
    # print(obj1.__three_attr)
    print(obj1._MyClass__three_attr)
    print(obj1.__four_attr__)

    per1 = Person('lemon', 19)
    # print(per.__name)
    print(per1.get_name())
    per1.set_name('keyou')
    print(per1.get_name())
    per1.sex = '男'

# 输出
abc
100
300
400
lemon
keyou

__dict__

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say_hello(self):
        print(f"Hello, my name is {self.name}. I'm {self.age} years old.")


p1 = Person("Alice", 25)
p1.gender = "female"
p1.__dict__["hometown"] = "New York"
print(p1.__dict__)
print(Person.__dict__.keys())  

# 输出
{'name': 'Alice', 'age': 25, 'gender': 'female', 'hometown': 'New York'}
dict_keys(['__module__', '__init__', 'say_hello', '__dict__', '__weakref__', '__doc__'])

现在,我们可以通过访问 p1.__dict__ 来查看该对象的所有属性和方法,它应该包含以下键值对:

{
    "name": "Alice",
    "age": 25,
    "gender": "female",
    "hometown": "New York",
    "say_hello": <function Person.say_hello at 0x7f9a61e4db80>
}

从中可以看到,除了 nameage 属性之外,我们还添加了 genderhometown 属性,并且 say_hello 方法也被包含在其中。

注意:实例对象的 __dict__ 属性只能访问和修改其自身的属性和方法,而无法访问其所属类的属性和方法。如果需要访问类的属性和方法,可以通过类的 __dict__ 属性来实现

例如:

print(Person.__dict__.keys())  # 返回 ['__module__', '__init__', 'say_hello', '__dict__', '__weakref__', '__doc__']

以上代码可以返回 Person 类的所有属性和方法,包括 __init__say_hello__dict__

__slots__

默认情况下,类的实例有一个字典用于存储属性。这对于具有很少实例变量的对象会浪费空间。当创建大量实例时,空间消耗可能会变得尖锐。

可以通过在类定义中定义___ slots __ 来覆盖默认 __dict__行为。__ slots __ 声明接受一个实例变量序列,并在每个实例中只保留足够保存每个变量值的空间。因为没有为每个实例创建 __dict__ ,所以节省空间。

定义过slots属性来限制类实例的属性,只能绑定slots指定的属性。不能添加slots之外的属性。

class Person:
    __slots__ = ('name', 'age')

    def __init__(self, name_, age_):
        self.name = name_
        self.age = age_


if __name__ == '__main__':
    per1 = Person('lemon', 18)
    print(per1.name)
    setattr(per1, 'attr', 100)  # AttributeError: 'Person' object has no attribute 'attr'

自定义属性访问

可以定义下面的方法来自定义类实例的属性访问的含义(访问、赋值或者删除 x.name )。

  • object.__getattr__

    • 当属性查找不到时会调用

  • object.__getattribute__

    • 查找属性时,第一时间会调用该方法

  • object.__setattr__

    • 设置属性时,调用该方法设置属性,

  • object.__delattr__

    • 在del obj.attr删除属性时触发。

class MyClass(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __getattribute__(self, item):
        """
        当获取属性时首先会调用此方法
        obj.attr
        getattr(obj, 'attr')
        :param item:
        :return:
        """
        value = super().__getattribute__(item)
        return value

    def __setattr__(self, key, value):
        """
        当设置属性时调用此方法
        obj.attr = value
        setattr(obj, 'attr', value)
        :param key:
        :param value:
        :return:
        """
        super().__setattr__(key, value)

    def __delattr__(self, item):
        """
        当删除属性时,会调用此方法
        del obj.attr
        delattr(obj, 'attr')
        :param item:
        :return:
        """
        super().__delattr__(item)

    def __getattr__(self, item):
        """
        当获取的属性不存在时,会调用此方法
        :param item:
        :return:
        """
        pass

if __name__ == '__main__':
    one_obj1 = MyClass(10, 20)
    print(one_obj1.__dict__)
    one_obj1.aa = 200   # 动态添加属性不推荐
    print(one_obj1.__dict__)
    setattr(one_obj1, 'aaa', 300)   # one_obj1.aaa = 300
    getattr(one_obj1, 'aaa')        # one_obj1.aaa
    delattr(one_obj1, 'aaa')        # del one_obj1.aaa
    print(one_obj1.a)  # 获取已经存在的属性
    print(one_obj1.zzz)  # 获取不存在的属性

# 输出
{'a': 10, 'b': 20}
{'a': 10, 'b': 20, 'aa': 200}
10
None
class AttrExample:
    """
    str_attr只能设置为字符串类型
    int_attr只能设置为int类型
    """

    def __setattr__(self, key, value):
        if key == 'str_attr':
            if isinstance(value, str):
                super().__setattr__(key, value)
            else:
                raise TypeError("str_attr只能设置为字符串类型")
        elif key == 'int_attr':
            if isinstance(value, int):
                super().__setattr__(key, value)
            else:
                raise TypeError("int_attr只能设置为int类型")
        else:
            raise TypeError(f"不能创建这个属性:{key}")

if __name__ == '__main__':

    attr_example = AttrExample()
    attr_example.str_attr = 'abc'
    # attr_example.str_attr = 100   # raise TypeError("str_attr只能设置为字符串类型")
    attr_example.int_attr = 200
    # attr_example.float_attr = 1.2   # raise TypeError(f"不能创建这个属性:{key}")

以上代码定义了 str_attrint_attr 属性的类 AttrExample,并在 __setattr__ 方法中对属性进行了限制:只有当属性名为 str_attr 时,属性值才能为字符串类型;只有当属性名为 int_attr 时,属性值才能为整数类型。如果属性名不为 str_attrint_attr,则会抛出 TypeError 异常,提示不能创建这个属性。