有没有办法“重载点运算符”?

26 投票
4 回答
11715 浏览
提问于 2025-04-16 14:54

我知道这个问题说得有点奇怪,但我想不出其他更好的表达方式。我有一个应用程序需要处理很大的json对象,我想能够简单地说:

object1.value.size.whatever.attributexyz

而不是

object1.get('value').get('size').get('whatever').get('attributexyz')

有没有什么聪明的方法可以捕捉到会出现的AttributeError错误,并检查一下数据结构里面这个属性是否对应它的任何值呢?

4 个回答

1

我也想做同样的事情,于是开始尝试,结果写出了一个类(见下文),可以实现相同的功能。

虽然这个类可能看起来不太好,但确实能用。

示例

使用 json.loads() 的结果实际上是一个字典(dict)或列表(list),所以你可以直接用这个调用的返回值。举个例子,传入一个字典,然后在使用之前检查一下某个属性是否存在:

dumm_dict = {
    "name" : "1289378F67A",
    "location" : "eastus"
}

dummy_generic = GenericObject(dumm_dict)

if GenericObject.has_property(dummy_generic, ["name"]):
    print(dummy_generic.name, dummy_generic.location)

class GenericObject:
    '''
        Class that breaks down a json object into a class that has attributes
        named the same as the json object so you don't have to keep using 
        dictionary indexing to get to what you are after.
    '''
    def __init__(self, properties : dict):
        """
            Constructor takes a dictionary OR a list and creates
            a generic object out of it. 

            In the case of a pure list, the property will be called 'list',
            otherwise properties are named with the corresponding dictionary
            key value. 
        """

        if isinstance(properties, dict):
            parsed = GenericObject._expand_dict(properties)
            for item in parsed.keys():
                self.__setattr__(item, parsed[item])
        elif isinstance(properties, list):
            parsed = GenericObject._expand_list(properties)
            self.__setattr__("list", parsed)
        else:
            print("UNKNOWN", properties)
            raise Exception("Unknown type....")

    @staticmethod 
    def has_property(generic_object, prop_list):
        """
            Determine if there is a property on a given GenericObject

            generic_object : Instance of GenericObject
            prop_list : List of strings of properties. If there is more than one it will 
                        march through the object assuming the sub property.
        """
        prop_available = False
        if isinstance(generic_object, GenericObject):
            stepped_object = generic_object
            for prop in prop_list:
                if isinstance(stepped_object, GenericObject) and hasattr(stepped_object, prop):
                    prop_available = True
                    stepped_object = getattr(stepped_object, prop)
                else: 
                    prop_available = False
                    break

        return prop_available

    @staticmethod 
    def find_property(generic_object, prop_name):
        """
            Return a list of Generic Objects that contain a given property name

            generic_object : Instance of GenericObject
            prop_name : Property name to find
        """
        return_generic_objects = []

        if isinstance(generic_object, GenericObject):
            gen_variables = vars(generic_object)
            for variable in gen_variables.keys():
                if variable == prop_name:
                    # It has one...
                    return_generic_objects.append(generic_object)

                if isinstance(gen_variables[variable], GenericObject):
                    # Property is a gen object
                    sub_obs = GenericObject.find_property(gen_variables[variable], prop_name)
                    return_generic_objects.extend(sub_obs)

                if isinstance(gen_variables[variable], list):
                    for sub_list_item in gen_variables[variable]:
                        # Property is a gen object
                        sub_obs = GenericObject.find_property(sub_list_item, prop_name)
                        return_generic_objects.extend(sub_obs)

        return return_generic_objects

    @staticmethod
    def dumpObject(generic_object, indent = 0, optional_end = ''):
        """
            dumpObject prints out the contents of a GenericObject instance
            so that the user can see that it was built correctly. 

            generic_object = A GenericObject instance
            indent = Number of spaces to indent printed lines
            optional_end = Optional line ending

            Both indent and optional_end are used internally, if you want to add one, 
            go for it, but it's not required.
        """

        indent_string = "" 
        if indent > 0:
            indent_string = " " * indent

        if isinstance(generic_object, GenericObject):
            v = vars(generic_object)
            for k in v.keys():
                if isinstance(v[k], GenericObject):
                    print(indent_string, k, '=')
                    GenericObject.dumpObject(v[k], indent + 2, optional_end)
                elif isinstance(v[k], list):
                    any_generics = False
                    for sub_item in v[k]:
                        if isinstance(sub_item, GenericObject):
                            any_generics = True
                            break

                    if any_generics:
                        print(indent_string, k, '= [')
                        for sub_item in v[k]:
                            GenericObject.dumpObject(sub_item, indent + 1, ',')
                            print(indent_string,'-------')
                        print(indent_string,']')
                    else:
                        print(indent_string, k,'=',v[k], optional_end)    

                else:
                    print(indent_string, k,'=',v[k], optional_end)    
        else:
            print(indent_string, generic_object, optional_end)

    @staticmethod
    def _expand_dict(props) -> dict:
        """
            Expands a dictionary and parses sub items in a dict or contained list
            into the correct format.
        """
        return_dict = {}
        for key in props.keys():
            if isinstance(props[key], dict):
                expanded = GenericObject(props[key])
                return_dict[key] = expanded
            elif isinstance(props[key], list):
                sub_list = []
                for sub_item in props[key]:
                    if isinstance(sub_item, dict):
                        sub_list.append(GenericObject(sub_item))
                    else:
                        sub_list.append(sub_item)
                return_dict[key] = sub_list
            else:
                return_dict[key] = props[key]
        return return_dict

    @staticmethod
    def _expand_list(props) -> list:
        """
            Expands a list and parses sub items in a dict or contained list
            into the correct format.
        """
        return_list = []
        for item in props:
            if isinstance(item, dict) or isinstance(item, list):
                expanded = GenericObject(item)
                if expanded:
                    return_list.append(expanded)
            else:
                return_list.append(item)
        return return_list
1

把这个结构放在一个对象里,并定义一个叫做 __getattr__() 的方法。如果你能控制这个结构的话,可以自己定义这个 __getattr__() 方法。这个方法的作用就是“捕捉”那些缺失的属性,并可能返回一些值。

38

object1的类定义中,

def __getattr__(self, key):
    return self.get(key)

如果你试图访问一个对象上并不存在的属性、方法或字段名,这个请求会被转发到__getattr__这个特殊的方法。

如果你无法访问类的定义,比如说它是一个字典,你可以把它放在一个类里面。对于字典,你可以这样做:

class DictWrapper(object):
    def __init__(self, d):
        self.d = d
    def __getattr__(self, key):
        return self.d[key]

注意,如果你访问的键是无效的,会抛出一个KeyError错误;不过,通常的做法是抛出一个AttributeError错误(感谢S. Lott的提醒!)。如果需要的话,你可以把KeyError错误重新抛出为AttributeError,方法如下:

try:
    return self.get(key)
except KeyError as e:
    raise AttributeError(e)

另外要记住,如果你从__getattr__返回的对象也是字典之类的,你也需要把它们包装起来。

撰写回答