【问题标题】:How does Python turn a function into a method?Python是如何将一个函数变成一个方法的?
【发布时间】:2021-03-17 07:36:04
【问题描述】:

我知道函数只是描述符,像这样:

def func(self):
    print(self.name)


class C:
    def __init__(self, name):
        self.name = name


C.func = func
c = C("foo")
c.func()

一开始我以为c.func等于C.func.__get__(c),是的,C.func.__get__(c)返回一个绑定方法。但是当我将func的__get__设置为None时,c.func仍然返回一个绑定的方法。

def func(self):
    print(self.name)


class C:
    def __init__(self, name):
        self.name = name


func.__get__ = None
C.func = func
c = C("foo")
c.func

输出:

<bound method func of <__main__.C object at 0x0000027EB23BF088>>

所以我很困惑。而且我发现Python在实例调用函数的时候,其实是调用了类的___getAttribute__方法,返回的是绑定的方法。

def func(self):
    print(self.name)

func.__get__ = None

class C:
    def __getattribute__(self, name):
        r = super().__getattribute__(name)
        print(r)  # r is a bound method already
        return r
        
    def __init__(self, name):
        self.name = name


C.func = func
c = C("foo")
c.func

输出:

<bound method func of <__main__.C object at 0x0000027EB243D1C8>>

func.__get__ 似乎没有任何效果。那么,__getattribute__ 发生了什么? Python是如何将一个函数变成一个方法的?我用谷歌搜索并做了一些研究,但我仍然找不到答案。

也许是我把事情弄复杂了,在我的理解中,函数本身就是一个描述符,但是就像下面的代码一样,我将func设置为None,它可以正常工作:

class C:
    def func(self):
        print('hello world')
        
    func.__get__ = None


c = C()
c.func()

但如果是描述符,它会引发TypeError

class C:
    class D:
        def __get__(self, inst, cls):
            if inst is None:
                return self
            return 'hello world'

    D.__get__ = None

    func = D()


c = C()
c.func

【问题讨论】:

  • 也许这可以回答你的问题:medium.com/@satishgoda/…
  • 非常感谢@Geekmoss。但是我还是一头雾水。看起来super().__getattribute__func变成了bound method,但是我不知道这是怎么回事,因为如果它是一个普通的描述符,它会调用它的__get__方法,但是它似乎没有调用func的__get__。我将func的__get__设置为None,但它仍然返回一个绑定的方法。
  • 我试着在下面回答,我希望这能回答你的问题。
  • 您不能在实例级别覆盖魔术方法 - Python 将完全忽略您的“覆盖”。
  • ⊙︿⊙,我明白了,谢谢@user2357112支持莫妮卡

标签: python


【解决方案1】:

好吧,如果我从我的发现中理解正确的话。 (因为不知道描述符,所以才喜欢帮忙,还在学习中)

首先,让我们看看__getattr____getattribute__

让我们有一个空类A

class A:
     pass

如果我初始化一个对象并尝试调用一个属性,因为目前没有,我们得到AttributeError

a = A()
a.some_property

会发生以下情况:

简单的流程检查:

class FlowDemo:
    def __init__(self):
        self.inited_property = True


    def __getattribute__(self, item):
        if item in ('__class__', '__len__') :  # For less spam of getting this attribute, if you want, you can remove condition.
            print('Get Attribute', item)
        # Call default behavior
        return super().__getattribute__(item)


    def __getattr__(self, item):
        print('Get Attr', item)
        if item == 'some_magic_name':
            return "It's magic!"
        raise AttributeError
fd = FlowDemo()

fd.inited_property
# Get Attribute inited_property
# True

fd.some_magic_property
# Get Attribute some_magic_name
# Get Attr some_magic_name
# "It's magic!"

fd.some_property
# Get Attribute some_property
# Get Attr some_property
# Traceback (most recent call last):
#  File "<input>", line 1, in <module>
#  File "stack-class-property-and-descriptors.py", line 67, in # __getattr__
#    raise AttributeError
# AttributeError

这大概是可以理解的,包括使用。但可以肯定的是,我将举一个例子。此逻辑用作数据库结果的动态表示(将属性映射到普通字典、列表等)。

但它也可以只是用于访问属性(property)的逻辑,例如访问计数器或验证(但这适用于__setattr____setattribute__

描述符呢?

首先让我们看一下数据描述符,它们对我来说更容易理解。 这是具有__get____set____delete__ 之一或两者的类或解码器。

一旦定义了这个,python,当在属性定义中使用它并且不返回一个类但它通过__get__获得的值时,在声明一个值时不会覆盖已经声明的类,而是使用它的__set__.

例子:

class WeekDayDescriptor:
    def __init__(self):
        self.__week_day = 0
        

    def __get__(self, instance, owner=None):
        return self.__week_day
    

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Value must be int')
        if not (0 <= value < 6):
            raise ValueError('Value must be in range 0 - 6')
        self.__week_day = value



class Calendar:
    week_day = WeekDayDescriptor()

    def __init__(self, week_day):
        self.week_day = week_day

演示:

c = Calendar(9)
# ValueError: Value must be in range 0-6

c = Calendar('6')
# TypeError: Value must be int

c = Calendar(3)
c.week_day = 6

c.week_day = 10
# ValueError: Value must be in range 0-6

c.week_day = 'monday'
# TypeError: Value must be int

装饰器描述符:

class Calendar:
    @property
    def week_day(self):
        return self.__week_day
    
    @week_day.setter
    def week_day(self, week_day):
        if not isinstance(week_day, int):
            raise TypeError('Value must be int')
        if not (0 <= week_day < 6):
            raise ValueError('Value must be in range 0 - 6')
        self.__week_day = week_day

    def __init__(self, week_day):
        self.week_day = week_day
    pass

现在对于非数据描述符...

非数据描述符是只有__get__ 的描述符。 据我了解,每个方法都会自动拥有自己的描述符,这要归功于函数获得对对象的引用 - self

我们可以为函数/方法编写自己的描述符,但这并不是那么简单,我们必须帮助自己并绕过它。

def function_as_method(self, value):
    print(self, value)

class HelperDescriptor:
    def __get__(self, instance, owner):
        def wrapper(*args, **kwargs):
            return function_as_method(instance, *args, **kwargs)
        return wrapper

class Foo:
    baz = HelperDescriptor()

>>> bar = Foo()
>>> bar.baz(1)
<__main__.Foo object at 0x7f64f7768b70> 1

Source of last code block, but in czech lang.


最后,您提到的问题是,当我们将 __get__ 设置为 None 时,您仍然会获得对该函数的引用。

很简单,python并没有直接区分原始数据类型和函数,都是有值的变量(或属性/属性)。无论是 value 还是 callable 都是另一回事。

def f(): return True
print(type(f), f())
# <class 'function'> True

f = 123
print(type(f), f)
# <class 'int'> 123

因此,当我们请求obj.func 方法或直接调用obj.func() 时,首先调用前两个更改魔法-__getattribute____getattr__。 如果我们调用一个方法,它只有在我们获得内存中的一个函数的引用后才会被调用。

再举个简单的例子:

def func(self, value):
    print('Printing:', value)


class PrintDescriptor:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        def wrapper(*args, **kwargs):
            print(f"Calling '{self.name}' method")
            return func(instance, *args, **kwargs)
        return wrapper


class B:
    foo = PrintDescriptor('foo')
    bar = PrintDescriptor('bar')

    def __getattribute__(self, item):
        if item not in ('__len__', '__class__', '__dict__'):
            print('Get Attribute', item)

        return super().__getattribute__(item)

演示:

b = B()

b.foo
# Get Attribute foo
# <function PrintDescriptor.__get__.<locals>.wrapper at 0x7f774a782ee0>

b.foo(2)
# Get Attribute foo
# Calling 'foo' method
# Printing: 2

b.bar(4)
# Get Attribute bar
# Calling 'bar' method
# Printing: 4

来源:

【讨论】:

  • 再次感谢您,@Geekmoss。不过我还是不太明白,又编辑了帖子,加了一些代码,我的英文不是很好,希望能说清楚。
猜你喜欢
  • 1970-01-01
  • 2020-11-19
  • 2020-08-22
  • 2013-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-18
  • 1970-01-01
相关资源
最近更新 更多