【问题标题】:Dynamically changing a function __name__ throws AttributeError: 'method' object has no attribute '__name__'动态更改函数 __name__ 会引发 AttributeError: 'method' object has no attribute '__name__'
【发布时间】:2019-04-10 16:44:07
【问题描述】:

我正在尝试编写一个可以捕获任何通用函数调用的类,然后尝试对其进行一些操作。

例如,我尝试使用来自here 的类似问题的答案。

但是,当我尝试通过分配 __name__ 属性来更改方法的名称时,它会引发 AttributeError: 'method' object has no attribute '__name__' 错误。

我使用的代码,改编自example above 是:

class A():
    def __init__(self):
        self.x = 1 # set some attribute

    def __getattr__(self,attr):
        return self.__get_global_handler(attr)

    def __get_global_handler(self, name):
        # Do anything that you need to do before simulating the method call
        handler = self.__global_handler
        handler.__name__ = name # Change the method's name
        return handler

    def __global_handler(self, *args, **kwargs):
        # Do something with these arguments
        print("I am an imaginary method with name %s" % self.__global_handler.__name__)
        print("My arguments are: " + str(args))
        print("My keyword arguments are: " + str(kwargs))

    def real_method(self, *args, **kwargs):
        print("I am a method that you actually defined")
        print("My name is %s" % self.real_method.__name__)
        print("My arguments are: " + str(args))
        print("My keyword arguments are: " + str(kwargs))

如果我注释更改方法名称的行(handler.__name__ = name # Change the method's name),它可以正常工作:

>>> a.imaginary_method
<bound method A.__global_handler of <__main__.A object at 0x00000150E64FC2B0>>

>>> a.imaginary_method(1, 2, x=3, y=4)
I am an imaginary method with name __global_handler
My arguments are: (1, 2)
My keyword arguments are: {'x': 3, 'y': 4}

但是,如果我取消注释该行(以强制更改名称),我会得到:

>>> a.imaginary_method
[...]
AttributeError: 'method' object has no attribute '__name__'

我期望得到的是这样的:

>>> a.imaginary_method
<bound method A.imaginary_method of <__main__.A object at 0x00000150E64FC2B0>>

>>> a.imaginary_method(1, 2, x=3, y=4)
I am an imaginary method with name imaginary_method
My arguments are: (1, 2)
My keyword arguments are: {'x': 3, 'y': 4}

那么,有没有办法像这样即时更改方法的名称?非常感谢!

【问题讨论】:

    标签: python python-3.x


    【解决方案1】:

    handler.__name__ = name 不起作用的原因是因为handler 是一个绑定方法,即它是一个对象,它封装了对函数的引用以及将获取该值的实例的self。您可以通过打印来确认这一点;它说“绑定方法”:

    >>> a.imaginary_method
    <bound method A.__global_handler of <__main__.A object at 0x00000150E64FC2B0>>
    

    您可以通过__func__属性访问底层函数并更改其名称:

    def __get_global_handler(self, name):
        handler = self.__global_handler
        handler.__func__.__name__ = name # Change the method's name
        handler.__func__.__qualname__ = __class__.__qualname__ + '.' + name
        return handler
    

    不过,这种方法的问题在于,所有“想象的方法”实际上都指的是同一个__global_handler 方法。如果您尝试创建两个具有不同名称的方法,您会发现它们实际上都具有相同的名称(因为它们是相同的方法):

    a = A()
    foo = a.foo
    bar = a.bar
    
    print(foo)  # <bound method A.bar of <__main__.A object at 0x00000212AFCC7198>>
    print(bar)  # <bound method A.bar of <__main__.A object at 0x00000212AFCC7198>>
    

    所以,更好的解决方案是每次都创建一个新函数:

    class MethodFactory:
        def __getattr__(self, name):
            def func(*args, **kwargs):
                print("I am an imaginary method with name", name)
                print("My arguments are:", args)
                print("My keyword arguments are:", kwargs)
    
            func.__name__ = name
            func.__qualname__ = __class__.__qualname__ + '.' + name
            return func
    
    a = MethodFactory()
    foo = a.foo
    bar = a.bar
    print(foo)  # <function MethodFactory.foo at 0x00000238BDADC268>
    print(bar)  # <function MethodFactory.bar at 0x00000238BF8AB730>
    

    如您所见,此实现的副作用是foobar 是常规函数而不是绑定方法。如果由于某种原因,您必须返回绑定方法,您可以通过调用其__get__ 方法手动将函数转换为绑定方法:

    class MethodFactory:
        def __getattr__(self, name):
            def func(self, *args, **kwargs):
                print("I am an imaginary method with name", name)
                print("My arguments are:", args)
                print("My keyword arguments are:", kwargs)
    
            func.__name__ = name
            func.__qualname__ = __class__.__qualname__ + '.' + name
            return func.__get__(self, type(self))
    
    a = MethodFactory()
    foo = a.foo
    bar = a.bar
    print(foo)  # <bound method MethodFactory.foo of <__main__.MethodFactory object at 0x00000137BF0C29E8>>
    print(bar)  # <bound method MethodFactory.bar of <__main__.MethodFactory object at 0x00000137BF0C29E8>>
    

    【讨论】:

      猜你喜欢
      • 2017-10-16
      • 2022-12-01
      • 2023-01-02
      • 2021-07-17
      • 1970-01-01
      • 1970-01-01
      • 2022-08-15
      • 2020-04-26
      • 2020-11-21
      相关资源
      最近更新 更多