简体   繁体   English

Python:如何实现 __getattr__()?

[英]Python: how to implement __getattr__()?

My class has a dict, for example:我的班级有一个字典,例如:

class MyClass(object):
    def __init__(self):
        self.data = {'a': 'v1', 'b': 'v2'}

Then I want to use the dict's key with MyClass instance to access the dict, for example:然后我想将字典的键与 MyClass 实例一起使用来访问字典,例如:

ob = MyClass()
v = ob.a   # Here I expect ob.a returns 'v1'

I know this should be implemented by __getattr__, but I'm new to Python, I don't exactly know how to implement it.我知道这应该由 __getattr__ 实现,但我是 Python 新手,我不知道如何实现它。

class MyClass(object):

    def __init__(self):
        self.data = {'a': 'v1', 'b': 'v2'}

    def __getattr__(self, attr):
        return self.data[attr]

>>> ob = MyClass()
>>> v = ob.a
>>> v
'v1'

Be careful when implementing __setattr__ though, you will need to make a few modifications:但是在实现__setattr__时要小心,你需要做一些修改:

class MyClass(object):

    def __init__(self):
        # prevents infinite recursion from self.data = {'a': 'v1', 'b': 'v2'}
        # as now we have __setattr__, which will call __getattr__ when the line
        # self.data[k] tries to access self.data, won't find it in the instance 
        # dictionary and return self.data[k] will in turn call __getattr__
        # for the same reason and so on.... so we manually set data initially
        super(MyClass, self).__setattr__('data', {'a': 'v1', 'b': 'v2'})

    def __setattr__(self, k, v):
        self.data[k] = v

    def __getattr__(self, k):
        # we don't need a special call to super here because getattr is only 
        # called when an attribute is NOT found in the instance's dictionary
        try:
            return self.data[k]
        except KeyError:
            raise AttributeError

>>> ob = MyClass()
>>> ob.c = 1
>>> ob.c
1

If you don't need to set attributes just use a namedtuple eg.如果您不需要设置属性,只需使用命名元组,例如。

>>> from collections import namedtuple
>>> MyClass = namedtuple("MyClass", ["a", "b"])
>>> ob = MyClass(a=1, b=2)
>>> ob.a
1

If you want the default arguments you can just write a wrapper class around it:如果你想要默认参数,你可以围绕它编写一个包装类:

class MyClass(namedtuple("MyClass", ["a", "b"])):

    def __new__(cls, a="v1", b="v2"):
        return super(MyClass, cls).__new__(cls, a, b)

or maybe it looks nicer as a function:或者它作为一个函数看起来更好:

def MyClass(a="v1", b="v2", cls=namedtuple("MyClass", ["a", "b"])):
    return cls(a, b)

>>> ob = MyClass()
>>> ob.a
'v1'

Late to the party, but found two really good resources that explain this better (IMHO).聚会迟到了,但发现两个非常好的资源可以更好地解释这一点(恕我直言)。

As explained here , you should use self.__dict__ to access fields from within __getattr__ , in order to avoid infinite recursion.正如解释在这里,你应该使用self.__dict__内接入领域从__getattr__ ,为了避免无限递归。 The example provided is:提供的例子是:

 def __getattr__(self, attrName): if not self.__dict__.has_key(attrName): value = self.fetchAttr(attrName) # computes the value self.__dict__[attrName] = value return self.__dict__[attrName]

Note: in the second line (above), a more Pythonic way would be ( has_key apparently was even removed in Python 3):注意:在第二行(上面)中,一种更 Pythonic 的方式是( has_key显然在 Python 3 中甚至被删除了):

if attrName not in self.__dict__:

Theother resource explains that the __getattr__ is invoked only when the attribute is not found in the object, and that hasattr always returns True if there is an implementation for __getattr__ . 另一个资源解释了__getattr__仅在对象中找不到属性时调用,并且如果__getattr__有实现,则hasattr总是返回True It provides the following example, to demonstrate:它提供了以下示例,以进行演示:

 class Test(object): def __init__(self): self.a = 'a' self.b = 'b' def __getattr__(self, name): return 123456 t = Test() print 'object variables: %r' % t.__dict__.keys() #=> object variables: ['a', 'b'] print ta #=> a print tb #=> b print tc #=> 123456 print getattr(t, 'd') #=> 123456 print hasattr(t, 'x') #=> True
class A(object):
  def __init__(self):
     self.data = {'a': 'v1', 'b': 'v2'}
  def __getattr__(self, attr):
     try:
       return self.data[attr]
     except Exception:
       return "not found"


>>>a = A()
>>>print a.a
v1
>>>print a.c
not found

I like to take this therefore.因此我喜欢接受这个。

I took it from somewhere, but I don't remember where.我从某个地方拿来的,但我不记得在哪里。

class A(dict):
    def __init__(self, *a, **k):
        super(A, self).__init__(*a, **k)
        self.__dict__ = self

This makes the __dict__ of the object the same as itself, so that attribute and item access map to the same dict:这使得对象的__dict__与其自身相同,因此属性和项目访问映射到相同的 dict:

a = A()
a['a'] = 2
a.b = 5
print a.a, a['b'] # prints 2 5

I figured out an extension to @glglgl's answer that handles nested dictionaries and dictionaries insides lists that are in the original dictionary:我想出了@glglgl 答案的扩展,它处理原始字典中的嵌套字典和字典内部列表:

class d(dict):
    def __init__(self, *a, **k): 
        super(d, self).__init__(*a, **k)
        self.__dict__ = self
        for k in self.__dict__:
            if isinstance(self.__dict__[k], dict):
                self.__dict__[k] = d(self.__dict__[k])
            elif isinstance(self.__dict__[k], list):
                for i in range(len(self.__dict__[k])):
                    if isinstance(self.__dict__[k][i], dict):
                        self.__dict__[k][i] = d(self.__dict__[k][i])

A simple approach to solving your __getattr__() / __setattr__() infinite recursion woes解决__getattr__() / __setattr__()无限递归问题的简单方法

Implementing one or the other of these magic methods can usually be easy.实现这些魔术方法中的一种或另一种通常很容易。 But when overriding them both, it becomes trickier.但是当覆盖它们两者时,它变得更加棘手。 This post's examples apply mostly to this more difficult case.这篇文章的例子主要适用于这个更困难的案例。

When implementing both these magic methods, it's not uncommon to get stuck figuring out a strategy to get around recursion in the __init__() constructor of classes.在实现这两种神奇的方法时,在类的__init__()构造函数中找出一种绕过递归的策略并不少见。 This is because variables need to be initialized for the object, but every attempt to read or write those variables go through __get/set/attr__() , which could have more unset variables in them, incurring more futile recursive calls.这是因为需要为对象初始化变量,但是每次读取或写入这些变量的尝试都会通过__get/set/attr__() ,其中可能有更多未设置的变量,从而导致更多徒劳的递归调用。

Up front, a key point to remember is that __getattr__() only gets called by the runtime if the attribute can't be found on the object already .在前面,要记住的一个关键点是__getattr__()仅当在对象上找不到该属性时才会被运行时调用 The trouble is to get attributes defined without tripping these functions recursively.麻烦的是在不递归地触发这些函数的情况下获得定义的属性。

Another point is __setattr__() will get called no matter what.另一点是__setattr__()都会被调用。 That's an important distinction between the two functions, which is why implementing both attribute methods can be tricky.这是两个函数之间的一个重要区别,这就是为什么实现这两个属性方法可能会很棘手。

This is one basic pattern that solves the problem.这是解决问题的一种基本模式。

class AnObjectProxy:
    _initialized = False # *Class* variable 'constant'.

    def __init__(self):
        self._any_var = "Able to access instance vars like usual."
        self._initialized = True # *instance* variable.

    def __getattr__(self, item):
        if self._initialized:
            pass # Provide the caller attributes in whatever ways interest you.
        else:
            try:
                return self.__dict__[item] # Transparent access to instance vars.
            except KeyError:
                raise AttributeError(item)

    def __setattr__(self, key, value):
        if self._initialized:
            pass # Provide caller ways to set attributes in whatever ways.
        else:
            self.__dict__[key] = value # Transparent access.

While the class is initializing and creating it's instance vars, the code in both attribute functions permits access to the object's attributes via the __dict__ dictionary transparently - your code in __init__() can create and access instance attributes normally.当类正在初始化和创建它的实例变量时,两个属性函数中的代码都允许透明地通过__dict__字典访问对象的属性 - __init__()代码可以正常创建和访问实例属性。 When the attribute methods are called, they only access self.__dict__ which is already defined, thus avoiding recursive calls.当属性方法被调用时,它们只访问已经定义的self.__dict__ ,从而避免递归调用。

In the case of self._any_var , once it's assigned, __get/set/attr__() won't be called to find it again .self._any_var的情况下,一旦被分配, __get/set/attr__()将不会被调用以再次找到它

Stripped of extra code, these are the two pieces that are most important.除去额外的代码,这是最重要的两部分。

...     def __getattr__(self, item):
...         try:
...             return self.__dict__[item]
...         except KeyError:
...             raise AttributeError(item)
... 
...     def __setattr__(self, key, value):
...         self.__dict__[key] = value

Solutions can build around these lines accessing the __dict__ dictionary.解决方案可以围绕访问__dict__字典的这些行构建。 To implement an object proxy, two modes were implemented: initialization and post-initialization in the code before this - a more detailed example of the same is below.为了实现一个对象代理,在此之前的代码中实现了两种模式:初始化和后初始化——下面是一个更详细的例子。

There are other examples in answers that may have differing levels of effectiveness in dealing with all aspects of recursion.答案中还有其他示例,它们在处理递归的所有方面时可能具有不同程度的有效性。 One effective approach is accessing __dict__ directly in __init__() and other places that need early access to instance vars.一种有效的方法是在__init__()和其他需要提前访问实例变量的地方直接访问__dict__ This works but can be a little verbose.这有效,但可能有点冗长。 For instance,例如,

self.__dict__['_any_var'] = "Setting..."

would work in __init__() .将在__init__()

My posts tend to get a little long-winded.. after this point is just extra.我的帖子往往有点冗长……在这一点之后只是多余的。 You should already have the idea with the examples above.你应该已经有了上面的例子的想法。

A drawback to some other approaches can be seen with debuggers in IDE's.使用 IDE 中的调试器可以看到一些其他方法的缺点。 They can be overzealous in their use of introspection and produce warning and error recovery messages as you're stepping through code.当您单步执行代码时,他们可能会过度使用自省并产生警告和错误恢复消息。 You can see this happening even with solutions that work fine standalone.即使使用独立运行良好的解决方案,您也可以看到这种情况发生。 When I say all aspects of recursion, this is what I'm talking about.当我说递归的所有方面时,这就是我所谈论的。

The examples in this post only use a single class variable to support 2-modes of operation, which is very maintainable.这篇文章中的示例仅使用单个类变量来支持 2-mode 操作,这是非常易于维护的。

But please NOTE: the proxy class required two modes of operation to set up and proxy for an internal object.但请注意:代理类需要两种操作模式来设置和代理内部对象。 You don't have to have two modes of operation.您不必有两种操作模式。

You could simply incorporate the code to access the __dict__ as in these examples in whatever ways suit you.您可以像在这些示例中一样以适合您的任何方式简单地合并代码来访问__dict__

If your requirements don't include two modes of operation, you may not need to declare any class variables at all.如果您的要求不包括两种操作模式,您可能根本不需要声明任何类变量。 Just take the basic pattern and customize it.只需采用基本模式并对其进行自定义即可。

Here's a closer to real-world (but by no means complete) example of a 2-mode proxy that follows the pattern:这是一个更接近真实世界(但绝不是完整的)的遵循模式的 2 模式代理示例:

>>> class AnObjectProxy:
...     _initialized = False  # This class var is important. It is always False.
...                           # The instances will override this with their own, 
...                           # set to True.
...     def __init__(self, obj):
...         # Because __getattr__ and __setattr__ access __dict__, we can
...         # Initialize instance vars without infinite recursion, and 
...         # refer to them normally.
...         self._obj         = obj
...         self._foo         = 123
...         self._bar         = 567
...         
...         # This instance var overrides the class var.
...         self._initialized = True
... 
...     def __setattr__(self, key, value):
...         if self._initialized:
...             setattr(self._obj, key, value) # Proxying call to wrapped obj.
...         else:
...             # this block facilitates setting vars in __init__().
...             self.__dict__[key] = value
... 
...     def __getattr__(self, item):
...         if self._initialized:
...             attr = getattr(self._obj, item) # Proxying.
...             return attr
...         else:
...             try:
...                 # this block facilitates getting vars in __init__().
...                 return self.__dict__[item]
...             except KeyError:
...                 raise AttributeError(item)
... 
...     def __call__(self, *args, **kwargs):
...         return self._obj(*args, **kwargs)
... 
...     def __dir__(self):
...         return dir(self._obj) + list(self.__dict__.keys())

The 2-mode proxy only needs a bit of "bootstrapping" to access vars in its own scope at initialization before any of its vars are set. 2-mode 代理只需要一点“引导”就可以在初始化时访问它自己范围内的变量,然后再设置它的任何变量。 After initialization, the proxy has no reason to create more vars for itself, so it will fare fine by deferring all attribute calls to it's wrapped object.初始化后,代理没有理由为自己创建更多的变量,因此通过将所有属性调用推迟到它的包装对象,它会很好。

Any attribute the proxy itself owns will still be accessible to itself and other callers since the magic attribute functions only get called if an attribute can't be found immediately on the object.代理本身拥有的任何属性仍然可以被它自己和其他调用者访问,因为只有在无法立即在对象上找到属性时才会调用魔法属性函数。

Hopefully this approach can be of benefit to anyone who appreciates a direct approach to resolving their __get/set/attr__() __init__() frustrations.希望这种方法可以对任何欣赏直接方法来解决他们的__get/set/attr__() __init__()挫折的人有益。

You can initialize your class dictionary through the constructor:您可以通过构造函数初始化您的类字典:

    def __init__(self,**data):

And call it as follows:并按如下方式调用它:

f = MyClass(**{'a': 'v1', 'b': 'v2'})

All of the instance attributes being accessed (read) in __setattr__, need to be declared using its parent (super) method, only once:在 __setattr__中访问(读取)的所有实例属性,都需要使用其父(超级)方法声明一次:

    super().__setattr__('NewVarName1', InitialValue)

Or要么

    super().__setattr__('data', dict())

Thereafter, they can be accessed or assigned to in the usual manner:此后,可以以通常的方式访问或分配它们:

    self.data = data

And instance attributes not being accessed in __setattr__, can be declared in the usual manner:而在 __setattr__ 中没有被访问的实例属性,可以用通常的方式声明:

    self.x = 1

The overridden __setattr__ method must now call the parent method inside itself, for new variables to be declared:重写的 __setattr__ 方法现在必须在其自身内部调用父方法,以便声明新变量:

    super().__setattr__(key,value)

A complete class would look as follows:一个完整的类如下所示:

class MyClass(object):
    def __init__(self, **data):
        # The variable self.data is used by method __setattr__
        # inside this class, so we will need to declare it 
        # using the parent __setattr__ method:
        super().__setattr__('data', dict())
        self.data = data            
        # These declarations will jump to
        # super().__setattr__('data', dict())
        # inside method __setattr__ of this class:
        self.x = 1
        self.y = 2

    def __getattr__(self, name):
    # This will callback will never be called for instance variables
    # that have beed declared before being accessed.
        if name in self.data:
            # Return a valid dictionary item:
            return self.data[name]
        else:
            # So when an instance variable is being accessed, and
            # it has not been declared before, nor is it contained
            # in dictionary 'data', an attribute exception needs to
            # be raised.
            raise AttributeError

    def __setattr__(self, key, value):
        if key in self.data:
            # Assign valid dictionary items here:
            self.data[key] = value
        else:
            # Assign anything else as an instance attribute:
            super().__setattr__(key,value)

Test:测试:

f = MyClass(**{'a': 'v1', 'b': 'v2'})
print("f.a = ", f.a)
print("f.b = ", f.b)
print("f.data = ", f.data)
f.a = 'c'
f.d = 'e'
print("f.a = ", f.a)
print("f.b = ", f.b)
print("f.data = ", f.data)
print("f.d = ", f.d)
print("f.x = ", f.x)
print("f.y = ", f.y)
# Should raise attributed Error
print("f.g = ", f.g)

Output:输出:

f.a =  v1
f.b =  v2
f.data =  {'a': 'v1', 'b': 'v2'}
f.a =  c
f.b =  v2
f.data =  {'a': 'c', 'b': 'v2'}
f.d =  e
f.x =  1
f.y =  2
Traceback (most recent call last):
  File "MyClass.py", line 49, in <module>
    print("f.g = ", f.g)
  File "MyClass.py", line 25, in __getattr__
    raise AttributeError
AttributeError

I think this implement is cooler我觉得这个工具更酷

class MyClass(object):
    def __init__(self):
        self.data = {'a': 'v1', 'b': 'v2'}
    def __getattr__(self,key):
        return self.data.get(key,None)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM