为什么绑定类实例方法与绑定类方法不同?

时间:2020-08-31 06:15:08

标签: python

我正在阅读python docs,偶然发现以下几行:

同样重要的是要注意,作为类实例属性的用户定义函数不会转换为绑定方法。仅当函数是类的属性时,这种情况才会发生。

请,有人用简单的英语解释这是什么意思。

我将介绍一些速记符号:

让“ 用户定义的函数”由 f

let' class instance '用 ci 表示,而 class c 简单表示。显然(?),ci = c(),但有些滥用符号。

还允许以简单的集合符号重铸成员资格声明,例如,“ 用户定义的函数,它们是类实例的属性”的缩写为“ vf:fεa(ci) / em>”,其中 v :“ forall ”,其中“ a ”是(一组)属性(例如类或类实例),“ε”表示集合成员函数。

此外,绑定函数的过程由ci.f(* args)或cf(* args)=> f(ci,* args)或f(c,* args)简化表示(前者指调用 instance 方法调用,而稍后引用 class 方法调用)

使用新引入的速记符号,文档中的引号是否暗示

vf:fεa(c),c.f(* args)=> f(c,* args)是一个真实的语句

同时

vf:fεa(ci),ci.f(* args)=> f(ci,* args)是假的?

6 个答案:

答案 0 :(得分:12)

将用户定义的方法设置为类的属性,错误的方法

考虑以下示例类A和函数f


class A:
    pass

def f(self):
    print("I\'m in user-defined function")

a = A()

函数f是单独定义的,而不是在类内部定义的。

假设您要添加函数f作为a对象的实例方法。

通过将f设置为a属性来添加它是行不通的:

import types

class A:
    pass

def f(self):
    print("I\'m in user-defined function")

a = A()
a.f = f

# <function f at 0x000002D81F0DED30>
print(a.f)

# TypeError: f() missing 1 required positional argument: 'self'
# a.f()

因为函数f未绑定到对象a

这就是为什么在调用a.f()时会引发关于缺少参数的错误(如果f已绑定到a,则对象a是缺少参数)的原因self

这部分是文档所引用的:

还必须注意,作为类实例属性的用户定义函数不会转换为绑定方法。

当然,如果在类f中定义了函数A,这一切都不会发生,这就是文档的以下部分所述的内容:

...这仅在函数是类的属性时发生。

将用户定义的方法设置为类的属性,正确的方法

要将功能f添加到对象a,应使用:

import types

class A:
    pass

def f(self):
    print("I\'m in user-defined function")

a = A()

a.f = types.MethodType( f, a )

# <bound method f of <__main__.A object at 0x000001EDE4768E20>>
print(a.f)

# Works! I'm in user-defined function
a.f()

将用户定义的方法f绑定到实例a

答案 1 :(得分:3)

我认为花哨的形式逻辑符号在这里没有帮助。

但是,要回答这个问题:“作为类实例属性的用户定义函数不会转换为绑定方法;仅当函数是类的属性时才会发生这种情况”是什么意思?

一个绑定方法是一个依赖于类实例作为第一个参数的方法。它将实例作为第一个参数传递,该参数用于访问变量和函数。在Python 3和更高版本的python中,默认情况下,该类中的所有函数都是绑定方法。

因此,如果您将用户定义的函数创建为类实例的属性,则该函数不会自动转换为绑定方法。 “类实例”只是Python所说的“对象”或“对象实例”在其他语言中的含义的一种方式。

例如:

class HelloClass:
    greeting = 'Hello'

    def greet(self, name):
        print(f'{greeting} {name}')


hc = HelloClass()
hc.greet('John')

这里HelloClass是类,而hc是类实例。 greet是一个绑定方法,期望至少有一个参数(按惯例称为self),该参数在被调用时会自动分配给类实例-即在打印{{1之前,self的值}}是hello John类实例。

现在,如果您尝试这样做:

hc

这行得通(尽管您的IDE可能会反对),但这根本行不通:

def greet_with_hi(self, name):
    print(f'Hi {name}')


class HiClass:
    greet = greet_with_hi


hc = HiClass()
hc.greet('John')

它导致def greet_with_hi(self, name): print(f'Hi {name}') class HiClass: def __init__(self): self.greet = greet_with_hi hc = HiClass() hc.greet('John') 。而且应该这样,因为TypeError: greet_with_hi() missing 1 required positional argument: 'name'实例上的.greet不是绑定方法,并且HiClass self期望不会自动填充。

答案 2 :(得分:3)

以通常的方式创建方法时,它将是一个绑定方法:它将实例作为第一个参数(我们通常将其分配给“ self”):

class A:
    def meth(*args):
        print(args)
        
        
a = A()
a.meth()
        
# (<__main__.A object at 0x7f56a137fd60>,)  

如果您采用普通函数并将其添加到 class 属性中,则它将以相同的方式工作:

def f(*args):
    print(args)
    
A.f = f
a = A()
a.f()
# (<__main__.A object at 0x7f56a137f700>,)

实例作为第一个参数传递,这是一个绑定方法。

另一方面,如果您将该函数设为该类的 instance 的属性,则该方法将不是绑定方法=不会将实例作为第一个参数传递调用时:

a = A()
a.f = f
a.f()
# ()  

答案 3 :(得分:2)

我认为通过示例可以最好地阐明其含义。

假设我们有一个包含各种属性的类实例,这些属性是用户定义的函数。

  • add1是通过将其定义为类定义的一部分而添加的。
  • add2是通过在实例化之前对类进行猴子修补来添加的。
  • add3是在实例化后通过猴子修补类添加的。
  • add4是通过实例化后实例的猴子补丁添加的。
class Number:

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

    def add1(self):
        return self.x + 1

def add2(self):
    return self.x + 2

def add3(self):
    return self.x + 3

def add4(self):
    return self.x + 4

setattr(Number, 'add2', add2)

two = Number(2)

setattr(Number, 'add3', add3)

setattr(two, 'add4', add4)

print(two.add1())  # prints 3
print(two.add2())  # prints 4
print(two.add3())  # prints 5
print(two.add4())  # TypeError: add4() missing 1 required positional argument: 'self'

我们尝试称呼这些。

前三个全部正常工作(对于add3,在实例化时它不是该类的属性也没关系。

请注意,在调用这些函数时,我们不会显式传递与函数内第一个位置参数相对应的任何内容(即self),而是会自动为我们添加。这就是绑定方法的含义。它是用一个位置参数声明的,我们根本不传递任何参数。

但是在add4的情况下,它抱怨缺少位置参数-这是因为该实例不会自动添加为第一个参数。 (如果您明确使用two.add4(two),则可以使用。)

答案 4 :(得分:1)

绑定方法python 绑定方法是依赖于类实例作为第一个参数的方法。它将实例作为第一个参数传递,该参数用于访问变量和函数。在Python 3和更高版本的python中,默认情况下,该类中的所有函数都是绑定方法。

让我们通过一个例子来理解这个概念:

# Python code to demonstrate 
# use of bound methods 
  
class A: 
  
    def func(self, arg): 
        self.arg = arg 
        print("Value of arg = ", arg) 
  
  
# Creating an instance 
obj = A()   
  
# bound method 
print(obj.func) 
Output:

< bound method A.func of <__main__.A object at 0x7fb81c5a09e8>>

在这里

python将

obj.func(arg)转换为A.func(obj,arg)。 实例obj作为第一个参数自动传递给被调用的函数,因此该函数的第一个参数将用于访问对象的变量/函数。

让我们看看Bound方法的另一个示例。

# Python code to demonstrate 
# use of bound methods 
  
class Car: 
    # Car class created 
    gears = 5
  
    # a class method to change the number of gears  
    @classmethod
    def change_gears(cls, gears): 
        cls.gears = gears 
  
  
# instance of class Car created 
Car1 = Car() 
  
  
print("Car1 gears before calling change_gears() = ", Car1.gears) 
Car1.change_gears(6)  
print("Gears after calling change_gears() = ", Car1.gears) 
  
# bound method 
print(Car1.change_gears) 
Output:

Car1 gears before calling change_gears() =  5
Gears after calling change_gears() =  6
<bound method Car.change_gears of <class '__main__.Car'>>

上面的代码是类方法的示例。类方法类似于绑定方法,除了实例的类作为参数而不是实例本身作为参数传递。在上面的示例中,当我们调用Car1.change_gears(6)时,将“ Car”类作为第一个参数传递。

需要这些绑定方法 类中的方法将至少使用一个参数。为了使它们成为零参数方法,必须使用“装饰器”。一个类的不同实例具有与之关联的不同值。

例如,如果有一个“水果”类,则可能有苹果,橙子,芒果等实例。每个实例中的大小,颜色,味道和营养成分都可能不同。因此,要更改特定实例的任何值,该方法必须将“ self”作为参数,使其只能更改其属性。

示例:

class sample(object): 
  
    # Static variable for object number 
    objectNo = 0
  
    def __init__(self, name1): 
  
        # variable to hold name 
        self.name = name1 
  
        # Increment static variable for each object 
        sample.objectNo = sample.objectNo + 1
  
        # each object's unique number that can be 
        # considered as ID 
        self.objNumber = sample.objectNo 
  
    def myFunc(self): 
        print("My name is ", self.name,  
              "from object ", self.objNumber) 
  
    def alterIt(self, newName): 
        self.name = newName 
  
    def myFunc2(): 
        print("I am not a bound method !!!") 
  
  
# creating first instance of class sample         
samp1 = sample("A") 
samp1.myFunc() 
  
# unhide the line below to see the error 
# samp1.myFunc2() #----------> error line 
  
  
# creating second instance of class sample     
samp2 = sample("B") 
samp2.myFunc() 
samp2.alterIt("C") 
samp2.myFunc() 
samp1.myFunc() 
Output:

My name is  A from object  1
My name is  B from object  2
My name is  C from object  2
My name is  A from object  1

在上面的示例中,创建了两个实例samp1和samp2。请注意,将函数alterIt()应用于第二个实例时,仅更改了该特定实例的值。 samp1.myFunc()行将扩展为sample.myFunc(samp1)。对于此方法,不需要传递任何显式参数。实例samp1将作为参数传递给myFunc()。 samp1.myFunc2()行将生成错误:

Traceback (most recent call last):
  File "/home/4f130d34a1a72402e0d26bab554c2cf6.py", line 26, in 
    samp1.myFunc2() #----------> error line
TypeError: myFunc2() takes 0 positional arguments but 1 was given

这意味着该方法是未绑定的。它不接受任何实例作为参数。这些函数是未绑定的函数。

来源: Geeks For Geeks: Bound Methods Python

Geeks For Geeks: Bound, unbound and static methods in Python

答案 5 :(得分:1)

绑定方法基于here中记录的描述符。甚至还有一节“函数和方法”。

众所周知,描述符仅在类中起作用,而在实例中不起作用。记录在here中。

将这两条信息放在一起可以解释问题中引用的区别:

用户定义的函数是 类实例的属性不会转换为绑定方法; 仅当函数是类的属性时,这种情况才会发生。