Discuss / Python / 看完了评论,看到有好几个同学又跟我一样的疑问但是貌似没有得到解答

看完了评论,看到有好几个同学又跟我一样的疑问但是貌似没有得到解答

Topic source

牙神牙拔

#1 Created at ... [Delete] [Delete and Lock User]

用MethodType绑定到类和把方法直接写到类里为什么实例的属性值会不同?比如:

from types import MethodType
def set_age(self,age):
    self.age=age
class Stu(object):
    pass
Stu.set_age=MethodType(set_age,Stu)
A=Stu()
B=Stu()
A.set_age(10)
B.set_age(15)
print(A.age,B.age)#结果都是15

上面的例子为什么都是15,解释说实例A和实例B都没有age属性,原本的就是A.set_age就是给Stu类绑定属性age,给Stu绑定类属性为什么不是Stu.set_age(15)这样的方式?而是以实例A.B开头?

class Student(object):
    def set_age(self,age):
        self.age=age
A=Student()
B=Student()
A.set_age(10)
B.set_age(15)
print(A.age,B.age)#结果是10和15

MethodType是将方法绑定到实例或者类,廖老师也说过绑定类一般都会把方法写进去,原话是这样的:“给class绑定方法后,所有实例均可调用,通常情况下,上面的方法可以直接定义在class中”

这句话我理解的就是MethodType就相当于把方法直接写进class中,为什么会出现上面两种结果,求解释。

sunnyno001

#2 Created at ... [Delete] [Delete and Lock User]

感觉自己有那么一点概念,但是比较难形成专业的语言去描述,大概自己脑海里面也是模糊的吧。。。 先看下一节了,坐等专业解释。。。

这个问题问得太好了,你要不问,我估计我永远发现不了。 我来说说我的一些发现,和我的理解,不一定对,大家可以参考参考。 在你的代码的基础上,如果再新建一个Stu实例,比如说C=Stu(),你不用set_age,直接C.age就会输出15,而且Stu.age也是15。这说明A.age、B.age、C.age还有Stu.age都指向同一个内存区。至于为什么会这样,我来说说我的理解。 Stu类本身并没有属性和方法,所以用这个类创建的实例也没有属性和方法。用MethodType将set_age方法绑定到Stu类,并不是将这个方法直接写到Stu类内部,而是在Stu内存中创建一个link指向外部的方法,在创建Stu实例的时候这个link也会被复制。所以不管创建多少实例,这些实例和Stu类都指向同一个set_age方法。 A.set_age(10)并没有在A这个实例内部创建age属性,而是将age属性创建在外部set_age方法的内存区中。因为A和B内部link都指向外部set_age方法的内存区,所以不管A还是B在调用set_age方法的时候改变的是set_age方法内存区里的age属性,所以A改了B也就改了,所以新建的实例C在没有调用set_age方法的前提下也会有age属性,因为C的link指向的set_age方法的内存区,而set_age之前被A或者B调用过了。

运行以下代码,则会输出错误:AttributeError: type object 'A' has no attribute 'name' 也就是print_name参数self是指类A本身,类A是没有属性name的,只有属性text

from types import MethodType


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


class A(object):
    text = 'A'
    def __init__(self, name):
        self.name = name

A.print_name = MethodType(print_name, A)
a = A('a')
a.print_name()

这说明要访问实例属性还需要传入一个参数,也就是传入实例参数,代码如下:

from types import MethodType


def print_name(self, a):
    if isinstance(a,type(self)):
        print(self.text)
    else:
        print(a.name)


class A(object):
    text = 'A'
    def __init__(self, name):
        self.name = name

A.print_name = MethodType(print_name, A)
a = A('a')
b = A('b')
a.print_name(A)
b.print_name(a)
A.print_name(b)

注意a.print_name、b.print_name、A.print_name都是指向同一个函数,影响输出的是它们传入的第二个参数,它们传入的第一个参数都是类A

我是核仁

#5 Created at ... [Delete] [Delete and Lock User]

回复楼主: 将您的命令:

Stu.set_age=MethodType(set_age,Stu)

改为:

Stu.set_age = set_age

其他不变,然后执行命令

A=Stu()
B=Stu()
A.set_age(10)
B.set_age(15)

输出的结果是10 15

从这里可以看出,这是将方法绑定到类的方式引起。

这是python的帮助文档对MethodType作的解释: help(MethodType) Help on class method in module builtins:

class method(object) | method(function, instance) |
| Create a bound instance method object.

从上述解释看出,MethodType不适合用于给类绑定方法,它是给实例绑定方法的。

悠语214

#6 Created at ... [Delete] [Delete and Lock User]

讲一点个人的见解 运行如下的代码

from types import MethodType
def set_age(self,age):
self.age=age
class Stu(object):
pass
Stu.set_age=MethodType(set_age,Stu)
A=Stu()
A.set_age(10)
print(A.age,B.age)#结果都是10
B=Stu()
B.set_age(15)
print(A.age,B.age)#结果都是15

显然之后的

B.set_age(15)

使得A和B的age属性都变成了15,同时没进行运算的B的age属性也变了,这时候我推测是对B的age写入覆盖了A的age属性。(但是这个推测有问题,因为B并未进行运算) 随后运行如下的代码

from types import MethodType
def set_age(self,age):
self.age=age
class Stu(object):
pass
Stu.set_age=MethodType(set_age,Stu)
A=Stu()
A.set_age(10)
B=Stu()
print(A.age,B.age)#结果都是10

这意味的,B在没有进行如下运算的情况下

B.set_age(10)

仍然获得了age属性。 那么我之前的覆盖推测是错的。 接下来运行如下代码。

from types import MethodType
def set_age(self,age):
self.age=age
class Stu(object):
pass
Stu.set_age=MethodType(set_age,Stu)
A=Stu()
B=Stu()
A.set_age(10)
print(id(A.age))#获得A.age的内存地址
B.set_age(15)
print(id(A.age),id(B.age))#获得A.age和B.age的内存地址

结果在运行

A.set_age(10)

之后,显示A.age的地址为XXXXXXXXX。 而在运行

B.set_age(15)

之后,A.age的地址发生了改变,同时B.age的地址与A.age地址一样。 所以,这里A和B的age值其实是同一个值。

参见python帮助文档对MethodType的解释

help(MethodType) Help on class method in module builtins:

class method(object) | method(function, instance) | | Create a bound instance method object.

关于MethodType的用法是这样说明的: method(function, instance) 另外有说明"Create a bound instance method object.":创建一个绑定方法的实例。 而在

Stu.set_age=MethodType(set_age,Stu)

填入的实例是"Stu"这个类 这意味着所有的属于Stu这个类的的实例会全部受到Stu.set_age的影响 同时

Stu.set_age=MethodType(set_age,Stu)

中的"Stu.set_age"根本不重要,只是名字罢了,根本起不到对每个实例绑定的方法的作用。 运行如下代码测试

from types import MethodType
def set_age(self,age):
self.age=age
class Stu(object):
pass
TEST=MethodType(set_age,Stu)
A=Stu()
B=Stu()
TEST(10)
print(A.age,B.age)#结果都是10
TEST(15)
print(A.age,B.age)#结果都是15

那么推测是对的,MethodType前的名称对MethodType的生效范围没有任何影响。 而当用

Stu.set_age = set_age

替换

Stu.set_age=MethodType(set_age,Stu)

后 代码变成这样

from types import MethodType
def set_age(self,age):
self.age=age
class Stu(object):
pass
Stu.set_age = set_age
A=Stu()
B=Stu()
A.set_age(10)
B.set_age(15)
print(A.age,B.age)#结果为10 15

显然的,这里"Stu.set_age"的Stu是可以有效的指定到不同的实例的。

结论:MethodType只能用于给实例绑定方法。 当使用MethodType给类绑定方法时,方法是指定到类本身的,换而言之,绑定的方法成为了类本身的一个属性,会影响到整个类,类中的所有元素都会受到影响,获得此属性(包括之后加入此类的)。

minus_plus

#7 Created at ... [Delete] [Delete and Lock User]

我认为,通过MethodType将一个方法添加到类,这个方法是作为class method存在的,而且,在这个方法中添加的attributes也是class attributes。 所以才会出现你说的这种情况。

我用的是python2.7,但目测python3的结果应该是一样的。

class Student(object):
    pass

s = Student()

def set_age(self, age):
    self.age = age
s.set_age = MethodType(set_age, s)
s.set_age(19)

def set_score(self, score):
    self.score = score
Student.set_score = set_score
s2.set_score(100)

Student.set_gender = MethodType(set_gender, Student)
s.set_gender('male')



print hasattr(Student, 'gender')
print hasattr(Student, 'score')
print hasattr(Student, 'age')

# outputs:
True
False
False
>>> from types import MethodType
>>> def set_age(self,age):
...   self.age = age

>>> class Stu(object):
...  pass
... 
>>> Stu.set_age = MethodType(set_age,Stu)
>>> A = Stu()
>>> b = Stu()
>>> A.set_age(10)
>>> b.set_age(15)
>>> print(A.age,b.age)
15 15
>>> A.set_age(10)
>>> print(A.age,b.age)
10 10
>>> A.age = 100
>>> print(A.age,b.age)
100 10
>>> b.set_age(15)
>>> print(A.age,b.age)
100 15
>>> 
>>> Stu.age
15

个人理解:Stu本身是没有任何属性和函数的,因此又Stu创建的实例A,b也是没有任何属性和函数的,当对A,b分别调用set_age时,被动态绑定到Stu的set_age为Stu本身绑定了一个age属性,由于这是在A,b创建之后发生的,A,b仍然是没有绑定age属性的而是共享了,Stu的age,如上代码也证明了这一点。

爱梦男孩

#9 Created at ... [Delete] [Delete and Lock User]

真是好问题啊,回答也很精彩。

stu.set_age=Methodtype(stu.set_age,age)

这个是在对整个类定义属性。一旦定义之后,所有的age这个属性都是在对类进行赋值。所有这个类下的实例针对age这个值都会是在对类进行赋值。所以打印类的实例,也会是类的对应的属性的值。

大神真多,结论就是 MethodType只能用以实例,而强行用于类后,将作为类似于全局变量的存在影响在该类下的所有实例。


  • 1
  • 2

Reply