zoukankan      html  css  js  c++  java
  • python属性管理(1):基础

    管理属性的几种方式

    在python中访问、设置、删除对象属性的时候,有以下几种方式:

    1. 使用内置函数getattr()、setattr()和delattr()
    2. 自己编写getter()setter()deleter()方法
    3. 重载__getattr__()__setattr__()__delattr__()运算符,这决定了x.y的访问、赋值方式以及del x.y的方式
    4. 使用__getattribute__()方法
    5. 使用描述符协议
    6. 使用property协议,它是一种特殊的描述符

    本文简单介绍其中的前4种作为基础,后面使用单独的文章解释后2种。

    内置函数XXXattr()管理属性

    通过内置函数getattr()、setattr()、delattr()能简单访问、设置、删除对象上的属性。

    先看看它们的帮助文档:

    getattr(...)
        getattr(object, name[, default]) -> value
        Get a named attribute from an object;
        getattr(x, 'y') is equivalent to x.y.
    
    setattr(obj, name, value, /)
        Sets the named attribute on the given object to the specified value.
        setattr(x, 'y', v) is equivalent to ``x.y = v''
    
    delattr(obj, name, /)
        Deletes the named attribute from the given object.
        delattr(x, 'y') is equivalent to ``del x.y''
    

    用法很简单,给定要操作的对象obj以及要操作的属性名称name。对于getattr()来说,如果要操作的属性不存在默认会报错,可以给定一个default参数表示属性不存在时返回该给定属性值。

    例如,下面是一个简单的Person类和对象p:

    class Person():
        def __init__(self, name):
            self.name = name
    
    p = Person("malongshuai")
    

    使用getattr()获取name属性和不存在的age属性:

    print(getattr(p, "name"))
    print(getattr(p, "age", 23))
    

    上面访问age属性时,如果把第三个参数"23"去掉,将抛出异常。

    AttributeError: 'Person' object has no attribute 'age'
    

    使用setattr()和delattr()设置和删除属性:

    setattr(p, "age", 25)
    print(p.__dict__)
    
    delattr(p, "age")
    print(p.__dict__)
    

    返回结果:

    {'name': 'malongshuai', 'age': 25}
    {'name': 'malongshuai'}
    

    自己编写accessor方法

    一般面向对象的语言都是自己写setter、getter、deleter方法来管理属性的,通用又安全,但是管理起来并不那么方便。

    这里仅介绍一下,它们更好的写法参考:python设置对象属性

    例如,在Person类中加上name、age这两个属性的accessor方法:

    class Person():
        def __init__(self, name):
            self.name = name
    
        def set_name(self,name): self.name = name
        def get_name(self): return self.name
        def del_name(self): del self.name
    
        def set_age(self,age): self.age = age
        def get_age(self): return self.age
        def del_age(self): del self.age
    

    缺点是很明显的,对于想要管理的每个属性,都得去定义这些属性。也就是说,accessor方法是针对单个属性的。

    运算符重载管理属性

    通常可以直接使用点号运算来访问、设置属性。例如:

    p.name          # (1)访问p对象的name属性
    p.name = "abc"  # (2)为p对象的name属性赋值
    del p.name      # (3)删除p对象的name属性
    

    先说对象的赋值和删除操作,也就是上面的(2)和(3)。这两种操作可以直接被__setattr__()__delattr__()这两个方法拦截,或者说只要重写了这两个方法,每当对属性赋值、删除时,都会调用对应的这两个方法。

    再说访问属性的操作(1),python提供了两个对应的方法__getattr__()__getattribute__()。前者是在访问不存在的属性时被自动调用的,后者则是访问属性时被调用的,它无视属性是否存在

    这里提前说一个稍后要遇到的问题总结:对于适用于所有属性操作的__setattr____delattr____getattribute__方法,要避免它们的无限递归。参考后面的示例即可知。

    __getattr__()

    __getattr__()是通过点号访问不存在属性时被调用的。它有两个使用标准:要么返回属性值,要么抛出异常。

    例如:

    class Person():
        def __init__(self, name):
            self.name = name
    
        def __getattr__(self, attrname):
            if attrname == "name":
                print("in getattr1")
                return self.name
            elif attrname == "age":
                print("in getattr2")
                return 25
            else:
                print("in getattr3")
                raise AttributeError(attrname)
    
    p = Person("malongshuai")
    

    上面的Person类带有属性name,所以访问name属性的时候不会调用__getattr__(),而访问age或其它属性时会调用该方法,只不过age属性有自定义的返回值,其它属性则报错。

    print(p.name)
    print(p.age)
    print(p.job)
    

    以下是输出结果:

    malongshuai
    in getattr2
    25
    in getattr3
    Traceback (most recent call last):
      File "g:/pycode/b.py", line 21, in <module>
        print(p.job)
      File "g:/pycode/b.py", line 14, in __getattr__
        raise AttributeError(attrname)
    AttributeError: job
    

    __getattribute__()

    __getattribute__()__getattr__()类似,不同的是它前者适用于所有属性的访问,而不管目标属性是否存在。

    需要注意的是,__getattribute__()适用于所有属性访问操作,所以要避免无限递归。例如,下面是错误的写法:

    def __getattribute__(self, attr):
        return self.attr
    

    因为这个方法中的self.attr会继续触发__getattribute__的调用,从而出现无限递归问题。

    解决办法是通过父类来访问,比如super()或object类。

    super().__getattribute__(attr)
    object.__getattribute__(self, attr)
    

    __getattribute__()的优先级高于__getattr__(),前者存在的时候不会调用到后者,除非前者的代码中调用了后者,或者前者抛出了异常。

    例如:

    class Person():
        def __init__(self, name):
            self.name = name
    
        def __getattribute__(self, attr):
            print("in getattribute")
            return object.__getattribute__(self, attr)
            # return super.__getattribute__(attr)
    
        def __getattr__(self, attrname):
            if attrname == "name":
                print("in getattr1")
                return self.name
            elif attrname == "age":
                print("in getattr2")
                return 25
            else:
                print("in getattr3")
                raise AttributeError(attrname)
    
    
    p = Person("malongshuai")
    
    print(p.name)
    print(p.age)
    

    返回结果:

    in getattribute
    malongshuai
    in getattribute
    in getattr2
    25
    

    上面输出了name和age两个属性,但是输出"p.age"的时候该属性不存在,于是__getattribute__抛出异常,然后触发__getattr__

    需要注意的是,在解决无限递归问题上,后面的__setattr____delattr__还会有一种访问__dict__的方式,这不适合于这里的__getattribute__,因为访问这个字典也会触发__getattribute__从而继续导致无限递归。

    __setattr__()

    __setattr__()用来拦截对象属性赋值操作。例如:

    p.name = "long"
    

    会转换为调用p.__setattr__(self,name,"long")

    唯一需要注意的是避免赋值时的无限递归问题。因为在__setattr__()中的赋值语句self.attr = value会继续调用该方法,最终导致无限递归。

    所以在__setattr__()方法中,必须使用__dict__来获取属性并进行赋值,或者访问父类同名属性。所以,有下面几种方式避免无限递归调用。

    self.__dict__[attr] = value
    super().__setattr__(attr, value)
    object.__setattr__(self, attr, value)
    

    参考下面的示例。

    class Person():
        def __init__(self, name):
            self.name = name
    
        def __setattr__(self, attr, value):
            print("in setattr")
            #self.__dict__[attr] = value
            #super().__setattr__(attr, value)
            object.__setattr__(self, attr, value)
    
    p = Person("malongshuai")
    
    p.age = 33      # 自动调用__setattr__()
    print(p.age)
    

    执行结果:

    in setattr
    in setattr
    33
    

    可能已经发现问题所在了,上面输出了两次in setter,原因是__init__()中的赋值操作也会触发__setattr__()

    __delattr__()

    当调用del x.y的时候会自动触发__delattr__()的调用。

    同样需要注意的是避免赋值时的无限递归问题。因为在__delattr__()中的del语句可能会继续调用该方法,最终导致无限递归。所以在__delattr__()方法中,必须使用__dict__来获取属性并进行赋值,或者访问父类同名属性。所以,有下面几种方式避免无限递归调用。

    del self.__dict__[attr]
    super().__delattr__(attr)
    object.__delattr__(self, attr)
    

    例如:

    class Person():
        def __init__(self, name):
            self.name = name
    
        def __delattr__(self, attr):
            print("%s deleting" % (attr))
    
            #del self.__dict__[attr]
            #super().__delattr__(attr)
            object.__delattr__(self, attr)
    
            print("%s deleted" % (attr))
    
    
    p = Person("malongshuai")
    p.age = 33
    del p.age
    
  • 相关阅读:
    利用 chunked 类型响应实现后台请求的监听
    C/C++ 中的宏/Macro
    SSL/TLS 链接的建立/握手
    C/C++ 中 `printf` 格式化
    多媒体文件的容器与编解码器的关系
    Unix 开发中的 Make 三连
    shell 中长命令的换行处理
    C++ float vs double
    Xcode 中配置 clang-format 格式化 C++ 代码
    C++ `endl` 与 ` ` 的区别
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/10193396.html
Copyright © 2011-2022 走看看