一切都是属性(python)

对于菜鸟来说,python中如何创建类(Class)应该是很简单的,比如:

class Foo(object):
pass

这就是python的类(很无聊对吧?). 我们也可以实例化这个类:

f = Foo()

实际上, 我们如果输出这个实例的类型,会发现它就是个类:

>>> type(f)
<class '__main__.Foo'>

现在, 我们已经创建了Foo类并且实例化了一个对象f. 但是在实际应用中这肯定不够, 比如我们想定义对象的变量. 我们知道可以写init函数(注意这个函数是在new一个对象后才执行的), 比如:

class Foo(object):
def __init__(self, x, y):
self.x = x
self.y = y
>> f = Foo(100, 'abc')
>>> f.x
100
>>> f.y
'abc'

表面上看, 上面的例子是对f这个F类对象设置了x,y变量.但实际上, 类变量和实例变量在操作上是没有区别的. 到底该如何看待python中的类变量和实例变量呢?

关键在于Foo类中的init方法, 我们添加了一个新属性叫self, 它指向我们新创建的实例对象(f). 可以说属性是python的一个基本属性. 只要理解了属性, 那就对python有了更深入的理解.

python中的每个对象都有属性. 你可以通过python的内置函数dir来查看. 比如:

>>> s = 'abc'
>>> len(dir(s))
71
>>> dir(s)[:5]
['__add__', '__class__', '__contains__', '__delattr__', '__doc__']
>>> i = 123
>>> len(dir(i))
64
>>> dir(i)[:5]
['__abs__', '__add__', '__and__', '__class__', '__cmp__']
>>> t = (1,2,3)
>>> len(dir(t))
32
>>> dir(t)[:5]
['__add__', '__class__', '__contains__', '__delattr__', '__doc__']

由此可以看出, python中的基本数据类型(像int, str等)都有很多属性. 我们只是罗列出了前5个而已, 有兴趣的童鞋可以自己实验.

The thing is, these attribute names returned by “dir” are strings. How can I use this string to get or set the value of an attribute? We somehow need a way to translate between the world of strings and the world of attribute names.
我们发现上面例子中返回的属性都是字符串(string), 那我们如何获得这些属性呢?
python提供了内置的方法getattr, 如下所示:

>>> getattr(t, '__class__')
tuple

上面的例子等同于:

>>> t.__class__
tuple

所以, 在python中 ‘.’操作和’getattr’是一样的. 要说有区别那就是’.’操作更简单而’getattr’更灵活.

另外,python也提供了内置函数’setattr’, 它的作用等同于’.’操作的赋值.

>>> f = Foo()
>>> setattr(f, 'x', 5)
>>> getattr(f, 'x')
5
>>> f.x
5
>>> f.x = 100
>>> f.x
100

As with all assignments in Python, the new value can be any legitimate Python object. In this case, we’ve assigned f.x to be 5 and 100, both integers, but there’s no reason why we couldn’t assign a tuple, dictionary, file, or even a more complex object. From Python’s perspective, it really doesn’t matter.

如果我们给一个对象赋一个没有定义的属性值会如何呢? 这样也可以!

>>> f.new_attrib = 'hello'
>>> f.new_attrib
'hello'
>>> f.favorite_number = 72
>>> f.favorite_number
72

所以, 我们可以动态地创建和分配属性. 但是如果读取一个未定义的属性就会报错:

>>> f.no_such_attribute
AttributeError: 'Foo' object has no attribute 'no_such_attribute'

python对象有属性,并且可以动态获取或是修改.而python中的函数也是对象,所以函数也可以设置属性:

def hello():
return "Hello"
>>> hello.abc_def = 'hi there!'
>>> hello.abc_def
'hi there!'

python中的函数是对象所以有自己的属性.同样地,也可以设置属性.

所以, 上面的例子中(Foo)类中的init方法中可以不设置变量, 而在类实例化对象上采用动态赋值是完全可以的.

This is one of those conventions that is really useful to follow: Yes, you can create and assign object attributes wherever you want. But it makes life so much easier for everyone if you assign all of an object’s attributes in init, even if it’s just to give it a default value, or even None. Just because you can create an attribute whenever you want doesn’t mean that you should do such a thing.

既然一切对象都有属性, 那么我们看看类:

>>> class Foo(object):
pass

类也可以设置属性:

>>> Foo.bar = 100
>>> Foo.bar
100

类也是对象, 所以类也有属性. 但是我们如何理解类属性和类对象属性(比如在init函数中定义的x, y)呢?

这个问题其实很好理解. 主要区别在于函数定义和类定义. 函数主体只有在函数调用时才会执行, 而类主体是立即执行且只执行一次! 我们可以在类主体中执行函数代码:

class Foo(object):
print("Hello from inside of the class!")

同样的, 也可以什么都不做, 只定义属性:

class Foo(object):
x = 100

如果我们在类定义中设置变量, 实际情况是赋值不成功的. 这样是创建类属性. 所以

Foo.x

会返回100.

在类主体中定义变量会成为类属性, 那类中的函数呢? 其实函数也是属性:

>>> class Foo(object):
def blah(self):
return "blah"
>>> Foo.blah
<unbound method Foo.blah>

其实定义一个函数就等于在当前命名空间(通常是全局空间)定义一个新属性. 那么在类中定义一个函数也就等于为类创建了一个新属性(只是这个属性是函数而已).

换言之: 对象方法不是绑定在对象上. 当你在类(Foo)对象f上调用对象函数”f.blah()”时会调用类(Foo)的函数blah(),并传递对象f给”blah”的第一个参数(也就是self). 所以调用”f.blah()”和”Foo.blah(f)”的效果是完全相同的.

但是, 当我调用”f.blah()”时, python是如果知道要调用”Foo.blah()”的? f和F完全是2个不同的对象; f是Foo的一个实例, 而Foo是type的一个实例. python为什么会调用Foo的”blah()”?

这就要说道python的属性查找规则了. 对于变量, python遵循LEGB规则顺序查找: L(Local), E(Enclosing), G(Global), B(Builtin). 但是对于属性, python遵循不同的规则: 首先,在对象查找. 其次,在对象的类上查找. 以此类推, 直到最顶级的类(object).

对应我们的例子中python会先在f对象想查找blah属性, 未找到后会在它的类(Foo)上查找, 找到后执行.

所以, 在python中其实没有所谓的”对象变量”或”类变量”的. 某些情况属性定义在类上, 而某些情况属性又定义在对象上.(当然, 类也是type对象, 在这里我们不讨论). 这也解释了为什么人们通常都会在类上定义属性, 因为这些属性对于它的对象来讲都是可见的.Yes, that is true, but it sometimes makes more sense than others to do so.

What you really want to avoid is creating an attribute on the instance that has the same name as an attribute on the class. 请看下面的例子:

class Person(object):
population = 0
def __init__(self, first, last):
self.first = first
self.last = last
self.population += 1
p1 = Person('Reuven', 'Lerner')
p2 = Person('foo', 'bar')

看起来好像没什么问题. 但是很快发现Person.population一直是0, 但p1.populationp2.population都被设置成了1. 怎么会这样?

注意这行代码:

self.population += 1

它会这样执行:

self.population = self.population + 1

右边的”self.population”会先于左边的”self.population”计算. 因此, 对于每个实例来说, 先在自己的属性中查找population, 未找到后会在它的类上继续查找, 找到后返回0, 所以”self.population”都会是0, 然后执行+1, 返回1!

We can actually see what attributes were actually set on the instance and on the class, using a list comprehension:

class Foo(object):
def blah(self):
return "blah"
>>> [attr_name for attr_name in dir(f) if attr_name not in dir(Foo)]
[]
>>> [attr_name for attr_name in dir(Foo) if attr_name not in dir(object)]
['__dict__', '__module__', '__weakref__', 'blah']

In the above, we first define “Foo”, with a method “blah”. That method definition, as we know, is stored in the “blah” attribute on Foo. We haven’t assigned any attributes to f, which means that the only attributes available to f are those in its class.

希望这篇文章对你学习python有点帮助!

原文地址: http://blog.lerner.co.il/python-attributes/

I hope that this has helped to put Python’s attributes into perspective. If this sort of thing interests you, you might be interested in my full-day, live online class about object-oriented Python, which I’ll next be offering on October 29th. I’m also giving a full-day, live online class about functional programming in Python on October 27th, and a free (no obligation) hour-long Webinar on October 22nd about functional programming in Python.

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器