Python 嵌套子对象/链接属性上的getattr和setattr?

Python 嵌套子对象/链接属性上的getattr和setattr?,python,python-2.7,recursion,attributes,setattr,Python,Python 2.7,Recursion,Attributes,Setattr,我有一个对象(Person),它有多个子对象(Pet,Residence)作为属性。我希望能够动态设置这些子对象的属性,如下所示: class Person(object): def __init__(self): self.pet = Pet() self.residence = Residence() class Pet(object): def __init__(self,name='Fido',species='Dog'):

我有一个对象(
Person
),它有多个子对象(
Pet,Residence
)作为属性。我希望能够动态设置这些子对象的属性,如下所示:

class Person(object):
    def __init__(self):
        self.pet = Pet()
        self.residence = Residence()

class Pet(object):
    def __init__(self,name='Fido',species='Dog'):
        self.name = name
        self.species = species

class Residence(object):
    def __init__(self,type='House',sqft=None):
        self.type = type
        self.sqft=sqft


if __name__=='__main__':
    p=Person()
    setattr(p,'pet.name','Sparky')
    setattr(p,'residence.type','Apartment')
    print p.__dict__
目前我得到了错误的输出:
{'pet':,'residence':,'pet.name':'Sparky','residence.type':'plant'}

如您所见,在
Person
Pet
子对象上,不是设置
name
属性,而是在
Person
上创建一个新属性
Pet.name

  • 我无法将
    person.pet
    指定为
    setattr()
    ,因为不同的子对象将通过相同的方法进行设置,如果找到相关的键,该方法将解析一些文本并填充对象属性

  • 是否有一种简单/内置的方法来实现这一点

  • 或者我可能需要编写一个递归函数来解析字符串并多次调用
    getattr()
    ,直到找到必要的子对象,然后对找到的子对象调用
    setattr()


好的,所以在键入问题时,我想到了如何做,而且似乎效果不错。以下是我的想法:

def set_attribute(obj, path_string, new_value):
    parts = path_string.split('.')
    final_attribute_index = len(parts)-1
    current_attribute = obj
    i = 0
    for part in parts:
        new_attr = getattr(current_attribute, part, None)
        if current_attribute is None:
            print 'Error %s not found in %s' % (part, current_attribute)
            break
        if i == final_attribute_index:
            setattr(current_attribute, part, new_value)
        current_attribute = new_attr
        i+=1


def get_attribute(obj, path_string):
    parts = path_string.split('.')
    final_attribute_index = len(parts)-1
    current_attribute = obj
    i = 0
    for part in parts:
        new_attr = getattr(current_attribute, part, None)
        if current_attribute is None:
            print 'Error %s not found in %s' % (part, current_attribute)
            return None
        if i == final_attribute_index:
            return getattr(current_attribute, part)
        current_attribute = new_attr
        i += 1
我想这解决了我的问题,但我还是很好奇是否有更好的方法

我觉得这在OOP和python中一定很常见,所以我很惊讶gatattr和setattr本机不支持这一点。

您可以使用:

rgetattr
rsettr
getattr
setattr
的直接替代品, 它还可以处理虚线
attr
字符串




对于单亲和单子:

if __name__=='__main__':
    p = Person()

    parent, child = 'pet.name'.split('.')
    setattr(getattr(p, parent), child, 'Sparky')

    parent, child = 'residence.type'.split('.')
    setattr(getattr(p, parent), child, 'Sparky')

    print p.__dict__

对于这个特定的用例,这比其他答案更简单。

我根据ubntu的答案制作了一个简单的版本,名为,通过解析和遍历ast,它也适用于属性、列表和dict

例如,对于此类:

class Person:
    settings = {
        'autosave': True,
        'style': {
            'height': 30,
            'width': 200
        },
        'themes': ['light', 'dark']
    }
    def __init__(self, name, age, friends):
        self.name = name
        self.age = age
        self.friends = friends


bob = Person(name="Bob", age=31, friends=[])
jill = Person(name="Jill", age=29, friends=[bob])
jack = Person(name="Jack", age=28, friends=[bob, jill])
你可以这样做

# Nothing new
assert magicattr.get(bob, 'age') == 31

# Lists
assert magicattr.get(jill, 'friends[0].name') == 'Bob'
assert magicattr.get(jack, 'friends[-1].age') == 29

# Dict lookups
assert magicattr.get(jack, 'settings["style"]["width"]') == 200

# Combination of lookups
assert magicattr.get(jack, 'settings["themes"][-2]') == 'light'
assert magicattr.get(jack, 'friends[-1].settings["themes"][1]') == 'dark'

# Setattr
magicattr.set(bob, 'settings["style"]["width"]', 400)
assert magicattr.get(bob, 'settings["style"]["width"]') == 400

# Nested objects
magicattr.set(bob, 'friends', [jack, jill])
assert magicattr.get(jack, 'friends[0].friends[0]') == jack

magicattr.set(jill, 'friends[0].age', 32)
assert bob.age == 32
它也不允许您/某人调用函数或赋值,因为它不使用eval或允许赋值/调用节点

with pytest.raises(ValueError) as e:
    magicattr.get(bob, 'friends = [1,1]')

# Nice try, function calls are not allowed
with pytest.raises(ValueError):
    magicattr.get(bob, 'friends.pop(0)')
unutbu的答案()有一个“bug”。在
getattr()
失败并被
default
替换后,它继续在
default
上调用
getattr

示例:
rgetattr(object(),“nothing.imag”,1)
在我看来应该等于
1
,但它返回
0

  • getattr(object(),'nothing',1)
    ==1
  • getattr(1,'imag',1)
    ==0(因为1是实的,没有复杂的组件)
解决方案 我修改了rgetattr,在第一个缺少的属性处返回
默认值

import functools

DELIMITER = "."

def rgetattr(obj, path: str, *default):
    """
    :param obj: Object
    :param path: 'attr1.attr2.etc'
    :param default: Optional default value, at any point in the path
    :return: obj.attr1.attr2.etc
    """

    attrs = path.split(DELIMITER)
    try:
        return functools.reduce(getattr, attrs, obj)
    except AttributeError:
        if default:
            return default[0]
        raise

我只是喜欢递归函数

def rgetattr(obj,attr):
    _this_func = rgetattr
    sp = attr.split('.',1)
    if len(sp)==1:
        l,r = sp[0],''
    else:
        l,r = sp

    obj = getattr(obj,l)
    if r:
        obj = _this_func(obj,r)
    return obj

基于jimbo1qaz的答案,一个简单易懂的三行程序,简化到了极限:

def rgetattr(obj, path, default):
    try:
        return functools.reduce(getattr, path.split(), obj)
    except AttributeError:
        return default
用法:

>>> class O(object):
...     pass
... o = O()
... o.first = O()
... o.first.second = O()
... o.first.second.third = 42
... rgetattr(o, 'first second third', None)
42
请记住,“空格”不是这个用例的典型分隔符。

这应该是一个

def getNestedAttr(obj,nestedParam):
下一个=obj
对于nestedParam.split('.')中的p:
next=getattr(next,p)
下一个返回
班级问题:通过
issue=issue()
issue.status=issue()
issue.status.name=“您好”
getattr(问题“status.name”)
'''
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
AttributeError:'Issue'对象没有属性'status.name'
'''
getNestedAttr(问题'status.name')

#“你好”
这里有一些类似于ChaimG的答案,但它适用于任意数量的情况。但是,它只支持get属性,不支持设置它们

requested_attr = 'pet.name'
parent = Person()

sub_names = requested_attr.split('.')
sub = None

for sub_name in sub_names:

    try:
        sub = parent.__getattribute__(sub_name)
        parent = sub

    except AttributeError:
        raise Exception("The panel doesn't have an attribute that matches your request!")

pets_name = sub

对于开箱即用的解决方案,可以使用
操作符.attrgetter

from operator import attrgetter
attrgetter(dotted_path)(obj)

感谢您接受上述答案。这很有帮助。 如果有人想扩展hasattr的使用范围,请使用以下代码:

def rhasattr(对象、属性):
_嵌套的属性=属性拆分(“.”)
_curr_obj=obj
对于嵌套属性[:-1]:
如果hasattr(_curr_obj,_a):
_curr\u obj=getattr(\u curr\u obj,\u a)
其他:
返回错误
返回hasattr(\u curr\u obj,\u嵌套的\u attrs[-1])

最后一个属性的名称,
最终属性
可以出现多次。例如,
p.foo.foo
是合法的。因此,条件
part==final_属性
可能会触发得太快。啊,非常好的一点,我改为检查索引,这应该可以解决这个问题。谢谢你指出这一点!哇,这是我不知道的。谢谢你的回复!这太酷了,我喜欢你在5行中所做的事情,花了我30分钟。非常简洁的解决方案@RedX:该帖子已更新为包含一个
默认值
参数。我希望我能把它简化一点,但这就是生活。嗨,谢谢你的灵感,但我已经做了一个简化的实现,效果是一样的:@shouldsee:Python有一个。在Python中,它不是一个等价的递归解决方案。你能解释一下为什么*default在只访问它的第一个元素时是一个位置参数吗?啊,我现在明白了。对于其他人来说:这只是一种很容易决定是否给出默认值的方法。如果未设置默认值,元组
*default
将为空,因此
if default
将返回
False
。如果
default
有一个元素,那么我们可以将其作为默认值返回。通常这是通过初始化一个对象并将其命名为
\u DEFAULT
或类似的名称,并使用kwarg
DEFAULT=\u DEFAULT
来解决的,但是如果这个解决方案对以前没有见过它的人来说有点不明显,那么这个解决方案要简单得多(但将其命名为默认值)。这应该是公认的答案。我想我正在寻找一个对应的
attrsetter
,这样我就可以同时获取和设置-有没有办法用这种方法实现这一点?请注意
attrsetter
不支持默认参数l
>>> class O(object):
...     pass
... o = O()
... o.first = O()
... o.first.second = O()
... o.first.second.third = 42
... rgetattr(o, 'first second third', None)
42
requested_attr = 'pet.name'
parent = Person()

sub_names = requested_attr.split('.')
sub = None

for sub_name in sub_names:

    try:
        sub = parent.__getattribute__(sub_name)
        parent = sub

    except AttributeError:
        raise Exception("The panel doesn't have an attribute that matches your request!")

pets_name = sub
from operator import attrgetter
attrgetter(dotted_path)(obj)