我正在学习python,虽然我认为我得到了Python的整个概念和概念,但今天我偶然发现了一段我还不完全理解的代码:
假设我有一个应该定义Circles但没有身体的类:
class Circle():
pass
由于我没有定义任何属性,我该怎么做:
my_circle = Circle()
my_circle.radius = 12
奇怪的是Python接受上述语句。我不明白为什么Python不会引发undefined name error
。我明白通过动态类型我只需要随时将变量绑定到对象,但radius
类中不应存在属性Circle
以允许我这样做?
编辑:您的答案中有很多精彩的信息! 感谢大家所有这些精彩的答案!很遗憾,我只能将其作为答案。
答案 0 :(得分:47)
一个主要原则是没有宣言这样的东西。也就是说,你永远不会声明“这个类有一个方法foo”或“这个类的实例有一个属性栏”,更不用说关于要存储在那里的对象类型的声明了。您只需定义一个方法,属性,类等,然后添加它。正如JBernardo指出的那样,任何__init__
方法都会做同样的事情。任意限制创建名为__init__
的方法的新属性是没有多大意义的。并且有时将__init__
函数存储为实际上没有该名称的函数(例如装饰器)是有用的,并且这样的限制会破坏它。
现在,这并非普遍适用。内置类型将此功能省略为优化。通过__slots__
,您还可以在用户定义的类上禁止此操作。但这只是一个空间优化(不需要每个对象的字典),而不是正确的东西。
如果你想要一个安全网,那么太糟糕了。 Python没有提供一个,你不能合理地添加一个,最重要的是,它会被拥抱该语言的Python程序员所避开(阅读:几乎所有你想要使用的程序员)。测试和纪律,仍然有很长的路要走,以确保正确性。如果可以避免,则不要使用自由构成__init__
之外的属性,并进行自动化测试。由于这样的欺骗,我很少有AttributeError
或逻辑错误,并且在那些发生的情况下,几乎所有都被测试捕获。
答案 1 :(得分:34)
只是为了澄清这里讨论中的一些误解。这段代码:
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属性它
因此,尽管代码在编程上是等效的,但它在不同情况下使用。
答案 2 :(得分:18)
Python允许您在几乎任何实例(或类)上存储任何名称的属性。可以通过在C中编写类(如内置类型)或使用仅允许某些名称的__slots__
来阻止此操作。
它的工作原理是大多数实例将其属性存储在字典中。是的,像您使用{}
定义的常规Python字典。字典存储在名为__dict__
的实例属性中。事实上,有些人说“课程只是词典的语法糖”。也就是说,您可以使用带有字典的类来完成所有操作;课程只是让它变得更容易。
您已经习惯了静态语言,您必须在编译时定义所有属性。在Python中,类定义是执行,而不是编译;类就像任何其他类一样是对象;添加属性就像在字典中添加项一样简单。这就是Python被认为是动态语言的原因。
答案 3 :(得分:16)
不,python是这样灵活的,它不强制你可以存储在用户定义的类上的属性。
然而,有一个技巧,在类定义上使用__slots__
attribute将阻止您创建未在__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'
答案 4 :(得分:6)
它会创建radius
的{{1}}数据成员。
如果您曾向my_circle
提出要求,则会引发异常:
my_circle.radius
有趣的是,这不会改变班级;只是那个例子。所以:
>>> print my_circle.radius # AttributeError
答案 5 :(得分:4)
Python中有两种类型的属性 - Class Data Attributes
和Instance 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不同,其中每个类的实例都具有相同的实例变量集。
答案 6 :(得分:1)
正如delnan所说,您可以使用__slots__
属性获取此行为。但事实上,它是一种节省内存空间和访问类型的方式,并没有放弃它(也)是禁用动态属性的意思。
禁用动态属性是一件合理的事情,只是为了防止因拼写错误而产生的细微错误。 “测试和纪律”很好,但依靠自动验证肯定也没有错 - 也不一定是unpythonic。
此外,由于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'
答案 7 :(得分:0)
要控制新属性的创建,可以覆盖__setattr__
方法。每次调用my_obj.x = 123
时都会调用它。
请参见documentation:
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)
,仍然可以绕过检查。