Python中类的特殊行为

时间:2014-01-23 19:15:52

标签: python class oop attributes instances

我正在学习Python,但没有OOP经验。我在IDLE(Python 3.3)中输入以下行,并且对象的行为让我感到困惑:

>>> class IBAN:
    ISOcode = '  '
    checkDigits = '00'
    class CCC:
        bankCode = '0000'
        branchCode = '0000'
        checkBank = '0'
        checkAccount = '0'
        account = '0000000000'


>>> >>> numberOne = IBAN()
>>> numberOne.CCC
<class '__main__.IBAN.CCC'>
>>> numberOne.CCC.bankCode
'0000'
>>> numberOne.ISOcode = 'ES'
>>> IBAN.ISOcode
'  '
>>> numberOne.ISOcode
'ES'
>>> IBAN.ISOcode = 'FR'
>>> numberOne.ISOcode
'ES'
>>> IBAN.ISOcode = 'FR'
>>> IBAN.ISOcode
'FR'
>>> numberTwo = IBAN()
>>> numberTwo.ISOcode
'FR'
>>> IBAN.ISOcode = 'IT'
>>> IBAN.ISOcode
'IT'
>>> numberOne.ISOcode
'ES'
>>> numberTwo.ISOcode
'IT'
>>> IBAN.ISOcode is numberTwo.ISOcode
True
>>> 

为什么对象numberTwo的ISOcode属性与IBAN的ISOcode相同,但与对象numberOne的ISOcode不同?

我期待这样的事情:

>>> numberOne.ISOcode
'ES'
>>> numberTwo.ISOcode
'FR'

然而,这与我的结果不符。

我发现a similar question on StackOverflow,这让我觉得结果应该是:

>>> numberOne.ISOcode
'IT'
>>> numberTwo.ISOcode
'IT'

但这与我的结果不符。

任何人都可以解释或链接到解释Python中Classes行为的资源吗?我试图研究Guido van Rossum的Python教程和“Unifying types and classes in Python 2.2”,但我无法理解他们在说什么。

谢谢!

5 个答案:

答案 0 :(得分:4)

该类是与该类实例不同的对象。类具有属性,而实例具有属性。每次尝试访问实例的属性时,如果实例没有该属性,则会在该类上查找该属性。如果在实例上设置属性,它总是在实例上设置,即使这意味着它会影响类上同名属性。

在您的班级IBAN中,您在上定义了一个属性ISOcode。执行numberOne.ISOcode = 'ES'后,在实例上设置 new 属性。您已将numberOne.ISOcode的值与IBAN.ISOcode的值相关联。但是您创建的任何新实例(如numberTwo)仍然没有自己的实例ISOcode。然后,当您设置IBAN.ISOcode时,您正在更改属性。

您可以将class属性视为“默认”。当您执行obj.attr时,如果obj没有自己的属性attr,则会获取该类当时的值。如果更改类属性的值,则更改默认值;如果您稍后尝试在没有自己的属性的实例上访问该属性,他们将看到您创建的新默认值。

这是一个更精简的例子:

>>> class Foo(object):
...     attr = "Something"
>>> one = Foo()
>>> two = Foo()

首先,所有值都相同,因为我没有指定任何特定于实例的值,因此onetwo都从类中获取它们的值。

>>> Foo.attr
'Something'
>>> one.attr
'Something'
>>> two.attr
'Something'

现在我将为one设置一个新值。这不会影响Footwo仍会从Foo获得其值,因此two也不受影响。

>>> one.attr = "This is only for #1"
>>> Foo.attr
'Something'
>>> one.attr
'This is only for #1'
>>> two.attr
'Something'

现在我将在课程Foo上设置一个新值。这不会影响one,因为one有自己的值。但是由于two没有自己的值,它从类中获取它的值,因为我改变了类的值,这改变了我从two得到的值:

>>> Foo.attr = "This is the class value"
>>> Foo.attr
'This is the class value'
>>> one.attr
'This is only for #1'
>>> two.attr
'This is the class value'

要记住的是,每次执行时都会动态计算每个属性查找。创建实例不会自动将类属性从类复制到实例。当您访问实例上的属性时,实例会对自己说“我有自己的属性吗?如果有,请返回该值。如果没有,则返回类的值。”它每次尝试访问属性时都会询问;实例没有时刻成为班级的“独立”。

答案 1 :(得分:0)

您的示例中定义的

ISOcode是所谓的类属性,而不是实例属性

您应该在班级self.ISOcode方法中设置__init__,以使其对每个实例都是唯一的。否则该属性存储在类中。

class IBAN:
    def __init__(self):
        self.ISOcode = '  '

答案 2 :(得分:0)

通过在类属性上设置实例属性,您正在做一些令人困惑的名称阴影。

>>> class A():
    stuff = ''

>>> a = A()
>>> a.stuff
''
>>> a.stuff = 'thing' # sets an instance attribute
>>> a.stuff 
'thing'
>>> a.__class__.stuff
''
>>> a.__class__.stuff = 'blah'
>>> a.stuff # instance attributes are checked FIRST
'thing'
>>> aa = A()
>>> aa.stuff
'blah'

基本上,不要这样做。如果您打算将属性作为实例属性,请在__init__

中进行设置

答案 3 :(得分:0)

Python具有类属性和实例属性。当您指定类似

的值时
x.ISOcode = 'b'

它为实例创建成员属性。当您首先访问对象中的属性时,它会搜索成员属性,如果没有找到,则会在类属性中搜索。

考虑以下示例

class IBAN(object):
    ISOcode = 'a'

print IBAN.ISOcode // outputs a

x = new IBAN()
print x.ISOcode // outputs "a"
x.ISOcode = 'b' // creates a member variable for instance x instead of modifying class variable

print IBAN.ISOcode // outputs "a"
print x.ISOcode // outputs "b"
print x.__class__.ISOcode // outputs "a"

y = new IBAN()
print y.ISOcode // outputs "a"

IBAN.ISOcode = 'c'

print IBAN.ISOcode // outputs "c"
print x.ISOcode // outputs "b" because member variable is set 
print x.__class__.ISOcode // outputs "c"
print y.ISOcode // outputs "c" because it still uses class variable. no instance variable set for this instance

答案 4 :(得分:0)

您看到的行为是因为成员访问python对象的回退行为。 numberOneIBAN类的一个实例,当您尝试访问其成员时,例如使用numberOne.ISOCode,它将检查对象本身是否有一个名为的成员ISOCode。如果是,它将使用它。如果没有,那么它将回退到对象的类型,并检查该类型是否具有该名称的成员,并使用它。

但是,当您在对象上设置成员时,例如在numberOne.ISOCode = 'ES'时,它不执行回退行为,它或者使用该对象的现有成员,或者如果没有这样的成员,将创造它。

这可能没关系,因为你之前从未做过OOP,但Python做的事情与其他语言有点不同。你没有在python类中声明实例成员,所以当你直接在ISOCode类下设置IBAN时,你就会使它成为{{1}的成员类对象,而不是该类的单个实例。如果您希望类的所有实例都有一个字段,请在该类的IBAN构造函数中指定它。例如:

__init__

这将确保在创建实例时,它将拥有自己的#!/bin/python class IBAN: def __init__(self): self.ISOCode = ' ' 成员。