Python 为什么允许向已经实例化的对象添加属性?

Python 为什么允许向已经实例化的对象添加属性?,python,attributes,python-3.x,declaration,Python,Attributes,Python 3.x,Declaration,我正在学习python,虽然我想我已经掌握了python的全部概念和概念,但今天我偶然发现了一段我没有完全理解的代码: 假设我有一个类,它应该定义圆,但没有实体: class Circle(): pass 由于我没有定义任何属性,我如何才能做到这一点: my_circle = Circle() my_circle.radius = 12 奇怪的是Python接受了上述语句。我不明白Python为什么不引发未定义名称错误。我知道,通过动态类型,只要我想,我就可以随时将变量绑定到对象,但

我正在学习python,虽然我想我已经掌握了python的全部概念和概念,但今天我偶然发现了一段我没有完全理解的代码:

假设我有一个类,它应该定义圆,但没有实体:

class Circle():
    pass
由于我没有定义任何属性,我如何才能做到这一点:

my_circle = Circle()
my_circle.radius = 12
奇怪的是Python接受了上述语句。我不明白Python为什么不引发
未定义名称错误
。我知道,通过动态类型,只要我想,我就可以随时将变量绑定到对象,但是
Circle
类中不应该存在一个属性
radius
,允许我这样做吗


编辑:在你的答案中有很多精彩的信息谢谢大家的精彩回答很遗憾,我只能将一个标记为答案。

不,python是如此灵活,它不强制用户定义类上可以存储的属性

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

>>> 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'
>>类Foo(对象):
...     __插槽\uuux=()
... 
>>>f=Foo()
>>>f.bar=‘垃圾邮件’
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
AttributeError:“Foo”对象没有属性“bar”
>>>类Foo(对象):
...     __插槽(条形)
... 
>>>f=Foo()
>>>酒吧
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
属性错误:酒吧
>>>f.bar=‘垃圾邮件’

它创建
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

Python允许您在几乎任何实例(或类)上存储任何名称的属性。可以通过使用C编写类(如内置类型)或使用只允许某些名称的
\uuuuuuuuu slots\uuuuuu
来阻止此操作

它工作的原因是大多数实例将其属性存储在字典中。是的,像您用
{}
定义的常规Python字典。字典存储在名为
\uu dict\uu
的实例属性中。事实上,有人说“类只是字典的语法糖”,也就是说,你可以用一个有字典的类做任何事情;上课只是让它更容易


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

一个主要原则是不存在声明。也就是说,您永远不会声明“这个类有一个方法foo”或“这个类的实例有一个属性栏”,更不用说声明要存储在那里的对象的类型了。您只需定义一个方法、属性、类等,然后添加它。正如JBernardo所指出的,任何
\uuuu init\uuu
方法都做同样的事情。任意地将新属性的创建限制为名为
\uuuuu init\uuuu
的方法是没有多大意义的。有时将函数存储为
\uuuu init\uuuu
是很有用的,但实际上没有该名称(例如decorators),这样的限制将打破这一限制

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


如果你想要一个安全网,那就太糟糕了。Python不提供一个,您也不能合理地添加一个,最重要的是,接受该语言的Python程序员会避开它(阅读:几乎所有您想要使用的人)。测试和规范在确保正确性方面仍有很长的路要走。如果可以避免的话,不要使用liberty在
\uuuu init\uuuu
之外创建属性,并进行自动测试。我很少有
AttributeError
或由于这样的欺骗而产生的逻辑错误,并且发生的错误中,几乎所有都被测试捕获。

Python中有两种类型的属性-
类数据属性
实例数据属性

Python为您提供了动态创建
数据属性
的灵活性

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

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  
所以,您可以看到,我们已经创建了两个实例属性,一个在
\uuuuu init\uuuu
内部,另一个在创建实例之后在外部

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

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

  • 注意:-虽然可以通过实例访问类属性,但不能将其删除。。 此外,如果您试图通过实例修改类属性,实际上会创建一个实例属性,该实例属性会遮挡该类属性

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

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 att的Foo对象
>>> 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'