67

我正在研究python,虽然我认为我了解了Python的整个概念和概念,但今天我偶然发现了一段我没有完全理解的代码:

假设我有一个应该定义 Circles 但缺少主体的类:

class Circle():
    pass

由于我没有定义任何属性,我该怎么做:

my_circle = Circle()
my_circle.radius = 12

奇怪的是 Python 接受了上述声明。我不明白为什么 Python 不提出undefined name error. 我确实明白,通过动态类型,我只是在需要时将变量绑定到对象,但是类中不应该radius存在一个属性Circle来允许我这样做吗?

编辑:您的答案中有很多精彩的信息!谢谢大家所有这些精彩的答案!很遗憾,我只能将一个标记为答案。

4

9 回答 9

56

一个主要原则是没有声明这样的东西。也就是说,你永远不会声明“这个类有一个方法 foo”或“这个类的实例有一个属性 bar”,更不用说声明要存储在那里的对象的类型了。您只需定义一个方法、属性、类等,它就会被添加。正如 JBernardo 指出的那样,任何__init__方法都做同样的事情。将新属性的创建任意限制为 name 的方法没有多大意义__init__。有时存储一个__init__实际上没有该名称的函数(例如装饰器)很有用,而这样的限制会破坏这一点。

现在,这并非普遍正确。内置类型省略了此功能作为优化。通过__slots__,您还可以在用户定义的类上防止这种情况。但这仅仅是空间优化(不需要每个对象都有字典),而不是正确性。

如果你想要一个安全网,那么,太糟糕了。Python 没有提供一个,而且你不能合理地添加一个,最重要的是,拥抱该语言的 Python 程序员会避开它(阅读:几乎所有你想与之合作的人)。测试和纪律,对于确保正确性还有很长的路要走。__init__ 如果可以避免的话,不要随意编造属性,做自动化测试。由于这样的诡计,我很少AttributeError遇到逻辑错误,而且在发生的那些错误中,几乎所有错误都被测试捕获了。

于 2012-09-24T16:28:41.113 回答
46

只是为了澄清这里讨论中的一些误解。这段代码:

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

foo = Foo(5)

而这段代码:

class Foo(object):
    pass

foo = Foo()
foo.bar = 5

完全等价。真的没有区别。它做同样的事情。这种区别在于,在第一种情况下,它被封装了,很明显 bar 属性是 Foo 类型对象的正常部分。在第二种情况下,情况是否如此尚不清楚。

在第一种情况下,您不能创建没有 bar 属性的 Foo 对象(嗯,您可能可以,但不容易),在第二种情况下,除非您设置,否则 Foo 对象将没有 bar 属性。

因此,尽管代码在编程上是等效的,但它在不同的情况下使用。

于 2012-09-24T17:02:08.747 回答
20

Python 允许您在几乎任何实例(或类)上存储任何名称的属性。可以通过在 C 中编写类(如内置类型)或使用__slots__仅允许某些名称来阻止这种情况。

它起作用的原因是大多数实例将它们的属性存储在字典中。是的,一个普通的 Python 字典,就像你用{}. 字典存储在一个名为 的实例属性中__dict__。事实上,有人说“类只是字典的语法糖”。也就是说,你可以用字典做你能做的一切;课程只是让它更容易。

您习惯于必须在编译时定义所有属性的静态语言。在 Python 中,类定义是执行的,而不是编译的;类和其他对象一样是对象;添加属性就像在字典中添加项目一样简单。这就是为什么 Python 被认为是一种动态语言的原因。

于 2012-09-24T16:27:14.137 回答
18

不,python 就是这样灵活的,它不强制您可以在用户定义的类中存储哪些属性。

但是有一个技巧,在类定义上使用__slots__属性__slots__会阻止您创建序列中未定义的其他属性:

>>> class Foo(object):
...     __slots__ = ()
... 
>>> f = Foo()
>>> f.bar = 'spam'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> class Foo(object):
...     __slots__ = ('bar',)
... 
>>> f = Foo()
>>> f.bar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: bar
>>> f.bar = 'spam'
于 2012-09-24T16:22:31.473 回答
6

它创建 的radius数据成员my_circle

如果您要求它,my_circle.radius它将引发异常:

>>> print my_circle.radius # AttributeError

有趣的是,这并没有改变类;仅此一例。所以:

>>> my_circle = Circle()
>>> my_circle.radius = 5
>>> my_other_circle = Circle()
>>> print my_other_circle.radius # AttributeError
于 2012-09-24T16:24:16.633 回答
4

Python 中有两种类型的属性 -Class Data AttributesInstance Data Attributes.

Python 为您提供了动态创建Data Attributes的灵活性。

由于实例数据属性与实例相关,因此您也可以在__init__方法中执行此操作,也可以在创建实例后执行此操作。

class Demo(object):
    classAttr = 30
    def __init__(self):
         self.inInit = 10

demo = Demo()
demo.outInit = 20
Demo.new_class_attr = 45; # You can also create class attribute here.

print demo.classAttr  # Can access it 

del demo.classAttr         # Cannot do this.. Should delete only through class

demo.classAttr = 67  # creates an instance attribute for this instance.
del demo.classAttr   # Now OK.
print Demo.classAttr  

所以,你看到我们创建了两个实例属性,一个在内部__init__,一个在外部,在创建实例之后..

但不同的是,内部创建的实例属性__init__将被设置为所有实例,而如果在外部创建,则可以为不同的实例设置不同的实例属性。

这与 Java 不同,在 Java 中,类的每个实例都有相同的实例变量集。

  • 注意: - 虽然您可以通过实例访问类属性,但不能删除它。此外,如果您尝试通过实例修改类属性,您实际上会创建一个隐藏类属性的实例属性。
于 2012-09-24T16:34:27.953 回答
2

如何防止创建新属性?

使用类

要控制新属性的创建,您可以覆盖该__setattr__方法。每次调用都会my_obj.x = 123调用它。

请参阅文档

class A:
  def __init__(self):
    # Call object.__setattr__ to bypass the attribute checking
    super().__setattr__('x', 123)

  def __setattr__(self, name, value):
    # Cannot create new attributes
    if not hasattr(self, name):
      raise AttributeError('Cannot set new attributes')
    # Can update existing attributes
    super().__setattr__(name, value)

a = A()
a.x = 123  # Allowed
a.y = 456  # raise AttributeError

请注意,如果用户直接调用,仍然可以绕过检查object.__setattr__(a, 'attr_name', attr_value)

使用数据类

使用dataclasses,您可以使用 禁止创建新属性frozen=True。它还将阻止更新现有属性。

@dataclasses.dataclass(frozen=True)
class A:
  x: int


a = A(x=123)
a.y = 123  # Raise FrozenInstanceError
a.x = 123  # Raise FrozenInstanceError

注意:dataclasses.FrozenInstanceError是 AttributeError 的子类

于 2020-01-13T06:59:13.767 回答
1

__slots__正如delnan所说,您可以使用该属性获得此行为。但是它是一种节省内存空间和访问类型的方法这一事实并没有放弃它(也)是禁用动态属性的手段这一事实。

禁用动态属性是一个合理的做法,即使只是为了防止由于拼写错误导致的细微错误。“测试和纪律”很好,但依赖自动验证当然也没有错——也不一定是不合逻辑的。

此外,由于该attrs库在 2016 年达到了第 16 版(显然在最初的问题和答案之后),创建一个带有插槽的封闭类从未如此简单。

>>> import attr
...
... @attr.s(slots=True)
... class Circle:
...   radius = attr.ib()
...
... f = Circle(radius=2)
... f.color = 'red'
AttributeError: 'Circle' object has no attribute 'color'
于 2018-03-07T13:07:10.880 回答
0

为了添加到Conchylicultor 的答案,Python 3.10 添加了一个新参数到dataclass.

slots参数将__slots__在类中创建属性,防止在 之外创建新属性__init__,但允许分配给现有属性。

如果slots=True,分配给未定义的属性将抛出AttributeError.

这是一个带有slots和的示例frozen

from dataclasses import dataclass

@dataclass
class Data:
    x:float=0
    y:float=0

@dataclass(frozen=True)
class DataFrozen:
    x:float=0
    y:float=0

@dataclass(slots=True)
class DataSlots:
    x:float=0
    y:float=0

p = Data(1,2)
p.x = 5 # ok
p.z = 8 # ok

p = DataFrozen(1,2)
p.x = 5 # FrozenInstanceError
p.z = 8 # FrozenInstanceError

p = DataSlots(1,2)
p.x = 5 # ok
p.z = 8 # AttributeError
于 2022-02-10T16:25:09.480 回答