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

Nli*_*tis 55 python attributes declaration python-3.x

我正在学习python,虽然我认为我得到了Python的整个概念和概念,但今天我偶然发现了一段我并不完全理解的代码:

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

class Circle():
    pass
Run Code Online (Sandbox Code Playgroud)

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

my_circle = Circle()
my_circle.radius = 12
Run Code Online (Sandbox Code Playgroud)

奇怪的是Python接受上述语句.我不明白为什么Python没有引发undefined name error.我明白通过动态类型我只需要随时将变量绑定到对象,但是类中是否不radius存在Circle允许我这样做的属性?

编辑:你的答案中有很多精彩的信息!谢谢大家的所有精彩答案!遗憾的是我只能将一个标记为答案.

小智 47

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

现在,这并非普遍适用.内置类型将此功能省略为优化.通过__slots__,您还可以在用户定义的类上禁止此操作.但这只是一个空间优化(不需要每个对象的字典),而不是正确的东西.

如果你想要一个安全网,那么太糟糕了.Python没有提供一个,你不能合理地添加一个,最重要的是,它会被拥抱该语言的Python程序员所避开(阅读:几乎所有你想要使用的程序员).测试和纪律,仍然有很长的路要走,以确保正确性.__init__ 如果可以避免话,不要使用自由来弥补属性,并进行自动化测试.AttributeError由于这样的欺骗,我很少有或者出现逻辑错误,而且那些发生的事情几乎都被测试所捕获.

  • 它的方式很好但是灵活性,也引起了可读性问题,在维护其他人的代码时,我经常忘记对象在某个时候有什么属性。 (3认同)

Len*_*bro 38

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

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

foo = Foo(5)
Run Code Online (Sandbox Code Playgroud)

这段代码:

class Foo(object):
    pass

foo = Foo()
foo.bar = 5
Run Code Online (Sandbox Code Playgroud)

完全相同的.确实没有区别.它完全一样.这种区别在于,在第一种情况下,它被封装,并且很明显bar属性是Foo类型对象的正常部分.在第二种情况下,不清楚是否如此.

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

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


kin*_*all 19

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

它起作用的原因是大多数实例将其属性存储在字典中.是的,像你定义的常规Python字典{}.字典存储在名为的实例属性中__dict__.事实上,有些人说"课程只是词典的语法糖".也就是说,您可以使用带有字典的类来完成所有操作; 课程只是让它变得更容易.

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


Mar*_*ers 17

不,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'
Run Code Online (Sandbox Code Playgroud)

  • @NlightNFotis你没有.Python不是Java,你不应该尝试用Python编写Java.如果你想这样做,写Java. (7认同)
  • 在没有安全网的情况下分享编程的乐趣:) (5认同)
  • @NlightNFotis:Java的类型安全是一种幻觉.它*感觉*它更安全,更安全,你可以更信任代码,但你真的不能.蛋糕是一个谎言. (3认同)
  • `__slots__`是一种节省内存的方法; 它不应该用作锁定类的方法. (3认同)
  • @NlightNFotis:你通常*不在*python中.您使用单元测试并拥抱灵活性. (2认同)
  • @NlightNFotis他可以参考以下事实:(1)访问修改等可以通过反射来规避;(2)静态类型可以使用强制转换来规避;(3)类型系统受限制,因为Java只编码非常基本的保证,它只涵盖了应用程序正确性和安全性的一小部分.(有类型系统可以逐步保证更多,但逐渐变得难以使用和理解.) (2认同)
  • @NlightNFotis:Python的理念被描述为"我们都同意成年人".这意味着如果你想破坏约定并直接改变`foo._variable` - 也许是为了调试,或者为了避免foo中的一些错误 - 你可以.但是如果它破坏了某些东西,你就不会向foo的作者抱怨. (2认同)

ins*_*get 6

它创建了一个radius数据成员my_circle.

如果你问过my_circle.radius它会抛出异常:

>>> print my_circle.radius # AttributeError
Run Code Online (Sandbox Code Playgroud)

有趣的是,这不会改变班级; 只是那个例子.所以:

>>> my_circle = Circle()
>>> my_circle.radius = 5
>>> my_other_circle = Circle()
>>> print my_other_circle.radius # AttributeError
Run Code Online (Sandbox Code Playgroud)

  • 虽然你可以做'Circle.xyz = 5`并更改类而不是实例... (5认同)