【问题标题】:In python, why we can create a new attribute from an instance and not a method?在 python 中,为什么我们可以从实例而不是方法创建新属性?
【发布时间】:2020-03-31 11:42:22
【问题描述】:

在下面的代码中,

# An example class with some variable and a method 
class ExampleClass(object):

    def __init__(self):
        self.var = 10

    def dummyPrint(self):
        print ('Hello World!')

# Creating instance and printing the init variable
inst_a = ExampleClass()
# This prints --> __init__ variable = 10
print ('__init__ variable = %d' %(inst_a.var))
# This prints --> Hello World!
inst_a.dummyPrint()

# Creating a new attribute and printing it. 
# This prints --> New variable = 20
inst_a.new_var = 20
print ('New variable = %d' %(inst_a.new_var))

# Trying to create new method, which will give error
inst_a.newDummyPrint()

我可以在类之外创建一个新的属性 (new_var),使用实例。它有效。理想情况下,我期待它不会起作用。

同样我尝试创建新的方法 (newDummyPrint());这将打印 AttributeError: 'ExampleClass' object has no attribute 'newDummyPrint' 正如我所料。

我的问题是,

  1. 为什么创建新属性会起作用?
  2. 为什么创建新方法不起作用?

【问题讨论】:

  • inst_a.new_var = ... 将值分配给(新)属性。 inst_a.newDummyPrint() 不是“创建”一个新方法,它只是试图调用一个。
  • 您实际上并没有在任何地方创建inst_a.newDummyPrint。你只是直接跳起来试图打电话给它。如果您在没有先设置值的情况下直接尝试读取inst_a.new_var,您会遇到同样的错误。

标签: python oop


【解决方案1】:

正如 cmets 中已经提到的,您在此处创建新属性:

inst_a.new_var = 20

在下一行阅读之前。您没有在任何地方分配newDummyPrint,因此显然属性解析机制找不到它并最终引发AtributeError。如果您尝试访问任何其他不存在的属性,即inst_a.whatever,您将得到完全相同的结果。

请注意,由于在 Python 中一切都是对象(包括类、函数等),因此访问“数据”属性或方法之间没有真正的区别——它们都是属性(无论是类还是实例),而属性解析规则相同。在方法(或任何其他可调用属性)的情况下,调用操作发生在 属性已被解析之后。

要动态创建一个新的“方法”,您主要有两种解决方案:创建为类属性(这将使其可用于该类的所有其他实例),或作为实例属性(这将 - 显然 - 使它仅在这个确切的实例上可用。

第一个解决方案非常简单:定义您的函数并将其绑定到类:

# nb: inheriting from `object` for py2 compat

class Foo(object):
     def __init__(self, var):
         self.var = var


def bar(self, x):
    return self.var * x

# testing before:
f = Foo(42)
try:
    print(f.bar(2))
except AttribteError as e:
    print(e)

# now binds the function to the class:
Foo.bar = bar

# and test it:
print(f.bar(2))

# and it's also available on other instances:
f2 = Foo(6)
print(f2.bar(7))

创建每个实例的方法是一个(非常小的)涉及更多 - 您必须手动从函数中获取方法并将此方法绑定到实例:

def baaz(self):
    return "{}.var = {}".format(self, self.var)

# test before:
try:
    print(f.baaz())
except AttributeError as e:
    print(e)


# now binds the method to the instance
f.baaz = baaz.__get__(f, Foo)

# now `f` has a `baaz` method    
print(f.baaz())

# but other Foo instances dont
try:
    print(f2.baaz())
except AttributeError as e:
    print(e)

你会注意到我在第一种情况下谈到了函数,在第二种情况下谈到了方法。 A python "method" is actually just a thin callable wrapper around a function, an instance and a class,并由 function 类型通过 descriptor protocol 提供 - 当属性在类本身上解析时自动调用(=> 是在描述符协议中实现的类属性),但在解析时不会实例。这就是为什么在第二种情况下,我们必须手动调用描述符协议。

还请注意,此处可能存在的限制:首先,__magic__ methods(所有以两个前导和两个尾随下划线命名的方法)仅在类本身上查找,因此您不能在每个实例的基础上定义它们.然后,slots-based 类型和一些内置或 C 编码类型不支持动态属性。这些限制主要是出于性能优化的原因。

【讨论】:

  • 感谢布鲁诺的澄清。你的第一种方法让我明白了我的错误!注意:我是 Python 的 OOP 初学者。在练习时,我有这个疑问。再次感谢。
【解决方案2】:

当您使用空类定义emulating Pascal "record" or C "struct" 时,您可以动态创建新属性。否则,您尝试做的不是一个好的方式,也不是面向对象编程的一个好的模式。你可以阅读很多关于它的书。一般来说,您必须在类定义中清楚地说明该类的对象是什么,它的行为方式:动态修改其行为(例如添加新方法)可能会导致未知结果,这会使您在阅读时无法生活一个月后编写代码,调试时甚至更糟。 甚至还有一个叫做Ambiguous Viewpoint的反模式问题:

缺乏对建模观点的澄清导致问题 对象模型中的歧义。

无论如何,如果您正在使用 Python 并且 您发誓永远不会在生产中使用此代码,您可以编写存储 lambda 函数的新属性,例如

c = ExampleClass()
c.newMethod = lambda s1, s2: str(s1) + ' and ' + str(s2)
print(c.newMethod('string1', 'string2'))
# output is: string1 and string2

但这很丑,我不会从不这样做。

【讨论】:

  • 无论类定义是否为空,您都可以(技术上)添加新的属性和方法 - FWIW,您甚至可以根据需要动态更改对象的类(但这通常不是一个好方法主意)。 wrt/ 良好实践,确实最好避免滥用 Python 的动态特性,但你“应该”当然不应该避免在有意义的地方明智地使用它——而且它确实使在相当多的情况下有意义。例如,Django 的 ORM 使用此功能为模型外键创建反向关系描述符。
  • 另请注意,在上面的示例中,您没有向实例添加 method,而是添加了普通函数(方法可以访问实例) -在这个例子中,真正丑陋的是使用 lambda 而不是清晰的函数定义。
  • @brunodesthuilliers,我完全同意你的看法
  • @brunodesthuilliers 感谢您的回复。我对 Python 中的 OOP 非常陌生。在练习时,我遇到了这样的疑问(我参加了 MOOC)。谢谢两位的澄清!
  • @AravindD.Chakravarti 请注意,与 C++ / Java / C# 等主流静态语言相比,Python 的对象模型有点特殊——但一旦你了解它的工作原理,它就会非常强大。还有很多关于 OO 的“学习资源”(无论是什么语言)都完全无能(礼貌)。 OO 设计的最佳资源(一旦你掌握了基本概念)是the GOF's "Design pattern" 的第一部分,我强烈建议你自己获取一份。
猜你喜欢
  • 2023-03-04
  • 1970-01-01
  • 2020-04-28
  • 1970-01-01
  • 1970-01-01
  • 2016-06-11
  • 2015-02-03
  • 1970-01-01
  • 2014-06-29
相关资源
最近更新 更多