python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

时间:2022-05-03 21:45:15

1.python 面向对象

 
1.__init__() 创建对象时的初始化方法

__init__()是一个特殊方法(special method)。Python有一些特殊方法。Python会特殊的对待它们。特殊方法的特点是名字前后有两个下划线。

如果你在类中定义了__init__()这个方法,创建对象时,Python会自动调用这个方法。这个过程也叫初始化。

class happyBird(Bird):
def __init__(self,more_words):
print 'We are happy birds.',more_words

summer = happyBird('Happy,Happy!')

这里继承了Bird类,它的定义见上一讲。

屏幕上打印:

We are happy birds.Happy,Happy!

我们看到,尽管我们只是创建了summer对象,但__init__()方法被自动调用了。最后一行的语句(summer = happyBird...)先创建了对象,然后执行:

summer.__init__(more_words)

'Happy,Happy!' 被传递给了__init__()的参数more_words

2.类的属性

上一讲中提到,在定义方法时,必须有self这一参数。这个参数表示某个对象。对象拥有类的所有性质,那么我们可以通过self,调用类属性。

python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异
class Human(object):
laugh = 'hahahaha'
def show_laugh(self):
print self.laugh
def laugh_100th(self):
for i in range(100):
self.show_laugh() li_lei = Human()          
li_lei.laugh_100th()
python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

这里有一个类属性laugh。在方法show_laugh()中,通过self.laugh,调用了该属性的值。

还可以用相同的方式调用其它方法。方法show_laugh(),在方法laugh_100th中()被调用。

通过对象可以修改类属性值(引用类型)。但这是危险的。类属性(引用类型)被所有同一类及其子类的对象共享。类属性引用类型值的改变会影响所有的对象。

3.对象的属性(性质)

我们讲到了许多属性,但这些属性是类的属性。所有属于该类的对象会共享这些属性。比如说,鸟都有羽毛,鸡都不会飞。

在一些情况下,我们定义对象的性质,用于记录该对象的特别信息。比如说,人这个类。性别是某个人的一个性质,不是所有的人类都是男,或者都是女。这个性质的值随着对象的不同而不同。李雷是人类的一个对象,性别是男;韩美美也是人类的一个对象,性别是女。

当定义类的方法时,必须要传递一个self的参数。这个参数指代的就是类的一个对象。我们可以通过操纵self,来修改某个对象的性质。比如用类来新建一个对象,即下面例子中的li_lei, 那么li_lei就被self表示。我们通过赋值给self.attribute,给li_lei这一对象增加一些性质,比如说性别的男女。self会传递给各个方法。在方法内部,可以通过引用self.attribute,查询或修改对象的性质。

这样,在类属性的之外,又给每个对象增添了各自特色的性质,从而能描述多样的世界。

python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异
class Human(object):
def __init__(self, input_gender):
self.gender = input_gender
def printGender(self):
print self.gender li_lei = Human('male') # 这里,'male'作为参数传递给__init__()方法的input_gender变量。
print li_lei.gender
li_lei.printGender()
python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

在初始化中,将参数input_gender,赋值给对象的性质,即self.gender。

li_lei拥有了对象性质gender。gender不是一个类属性。Python在建立了li_lei这一对象之后,使用li_lei.gender这一对象性质,专门储存属于对象li_lei的特有信息。

对象的性质也可以被其它方法调用,调用方法与类属性的调用相似,正如在printGender()方法中的调用。

#3楼 2012-12-27 17:27 next163 
self相当于 *this
__init__() 差不多算是构造。
 

#4楼 2013-02-08 15:37 峻祁连 

非常好的教程!,感谢!

不过有一句不太明白,您提到 : (在方法中更改类变量属性的值是危险的,这样会影响根据这个类定义的所有对象的这一属性!!)

我做了个实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Human(object):
    Can_Talk = True
    Can_Walk = True
    Age = 0
    Name = ""
 
    def Say(self, msg):
        print "I am saying: " + msg
 
         
class Child(Human):
    def Cry(self):
        print "wa wa ...."
 
    def ShowAge(self):
        print self.Name, " is " , self.Age ," years old."
 
    def Grow(self, yr) :
        self.Age = yr
 
 
Jerry = Child()
Jerry.Name = "Jerry"
Jerry.Age = 3
Jerry.Grow(4)
Jerry.ShowAge()
 
Daniel = Child()
Daniel.Name = "Daniel"
Daniel.Grow(1)
Daniel.ShowAge()

输出结果: 
C:\Python27>python c:\if.py
Jerry is 4 years old.
Daniel is 1 years old.

我在Jerry实例的Grow()方法中更改了类变量属性Age的值,没发现影响到其他对象Daniel啊? 能详细解释一下吗?

 

#7楼[楼主] 2013-02-16 16:41 Vamei 

@峻祁连
你的这种写法可行,是因为你的属性都是immutable的(比如整数、字符串)。但如果属性是mutable的话(比如list),就会出现问题。比如下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Human(object):
    Can_Talk = True
    Can_Walk = True
    Age = 0
    Name = ["Li", "Lei"]
 
 
a = Human()
b = Human()
 
a.Age += 1
print a.Age
print b.Age
 
a.Name[0] = "Wang"
print a.Name
print b.Name

为什么immutable是可行的呢?原因是,在更改对象属性时,如果属性是immutable的,该属性会被复制出一个副本,存放在对象的__dict__中。你可以通过下面的方式查看:
print a.__class__.__dict__
print a.__dict__
注意到类中和对象中各有一个Age。一个为0, 一个为1。所以我们在查找a.Age的时候,会先查到对象的__dict__的值,也就是1。
但mutable的类属性,在更改属性值时,并不会有新的副本。所以更改会被所有的对象看到。

所以,为了避免混淆,最好总是区分类属性和对象的属性,而不能依赖上述的immutable属性的复制机制。

 

#9楼 2013-02-21 15:55 egger 

@峻祁连
不清楚Python的对象内存怎么分配的
C#的类(对象)的不同实例会开辟出新的内存,不同实例的相同属性内存不一样的
以此类推:你修改了Jerry的age这个属性的值,不会影响到Daniel 的age属性 两个age只是同名 但是地址不一样 没有必然联系

#10楼[楼主] 2013-02-21 22:12 Vamei 

@egger
具体的内存实现我没有查。
在Python中, 类的属性和对象的属性是两个概念,尽管在@峻祁连举出的例子中,Python(有时)会自动创建与类属性同名的对象属性,这有点让人混淆。
我觉得好的习惯是遵循简单的原则:使用__init__初始化对象属性值是好习惯

#11楼 2013-02-23 21:57 pang18a 

@egger
http://blog.csdn.net/hsuxu/article/details/7785835 
我也对这里有点迷惑 后来百度了下
大概的意思好像是说 如果是个可变类型的变量 那么赋值时传递的是引用修改的是同一块内存 如果是不可变类型的变量 赋值时 会重新开辟一块内存

#12楼[楼主] 2013-02-24 09:46 Vamei 

@pang18a
对,是这样的。

#13楼 2013-06-18 16:38 MeT 

(通过对象来修改类属性是危险的,这样可能会影响根据这个类定义的所有对象的这一属性!!)
关于这一句,情况是这样的:
1、对象不能修改类的属性,只能修改自己的,也就是说,修改了之后对同类的其他对象没有影响;
2、动态修改类属性可以用类名.属性 = xxx来进行修改;
3、修改的类属性一般会影响所辖对象的属性,除非对象在此之前对该属性进行过修改。

#14楼 2014-02-12 12:45 helloray 

@MeT 
这才是正解

#15楼 2014-02-17 14:03 特务小强 

immutable这是基础知识了。
 

#17楼 2014-03-20 17:07 yexuan910812 

所谓的类成员和对象成员,是不是就是就是Java中static成员与对象成员的区别?

#18楼 2014-03-27 14:41 Triangle23 

最后一个例子中定义的Human类,类中的gender不需要声明吗?
 

#20楼 2014-04-22 11:58 最真的梦 

python 类在实例化的时候,如果成员变量的为引用类型,那么实例化的属性值均为同样的引用值;
可以理解为,class在加载的时候,引用类型的数据就已经在内存块里了;
而在java类实例化的时候,会在内存中开辟一块新的内存,再赋值引用类型的给成员变量。

#21楼 2014-04-22 15:15 Seandor 

恩,list immutable,不能乱改。
 
 

2.特殊方法与多范式

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。

Python一切皆对象,但同时,Python还是一个多范式语言(multi-paradigm),你不仅可以使用面向对象的方式来编写程序,还可以用面向过程的方式来编写相同功能的程序(还有函数式、声明式等,我们暂不深入)。Python的多范式依赖于Python对象中的特殊方法(special method)。

特殊方法名的前后各有两个下划线。特殊方法又被成为魔法方法(magic method),定义了许多Python语法和表达方式,正如我们在下面的例子中将要看到的。当对象中定义了特殊方法的时候,Python也会对它们有“特殊优待”。比如定义了__init__()方法的类,会在创建对象的时候自动执行__init__()方法中的操作。

(可以通过dir()来查看对象所拥有的特殊方法,比如dir(1))

运算符

Python的运算符是通过调用对象的特殊方法实现的。比如:

'abc' + 'xyz'               # 连接字符串

实际执行了如下操作:

'abc'.__add__('xyz')

所以,在Python中,两个对象是否能进行加法运算,首先就要看相应的对象是否有__add__()方法。一旦相应的对象有__add__()方法,即使这个对象从数学上不可加,我们都可以用加法的形式,来表达obj.__add__()所定义的操作。在Python中,运算符起到简化书写的功能,但它依靠特殊方法实现。

Python不强制用户使用面向对象的编程方法。用户可以选择自己喜欢的使用方式(比如选择使用+符号,还是使用更加面向对象的__add__()方法)。特殊方法写起来总是要更费事一点。

尝试下面的操作,看看效果,再想想它的对应运算符

(1.8).__mul__(2.0)

True.__or__(False)

内置函数

与运算符类似,许多内置函数也都是调用对象的特殊方法。比如

len([1,2,3])      # 返回表中元素的总数

实际上做的是

[1,2,3].__len__()

相对与__len__(),内置函数len()也起到了简化书写的作用。

尝试下面的操作,想一下它的对应内置函数

(-1).__abs__()

(2.3).__int__()

表(list)元素引用

下面是我们常见的表元素引用方式

li = [1, 2, 3, 4, 5, 6]
print(li[3])

上面的程序运行到li[3]的时候,Python发现并理解[]符号,然后调用__getitem__()方法。

li = [1, 2, 3, 4, 5, 6]
print(li.__getitem__(3))

尝试看下面的操作,想想它的对应

li.__setitem__(3, 0)

{'a':1, 'b':2}.__delitem__('a')

函数

我们已经说过,在Python中,函数也是一种对象。实际上,任何一个有__call__()特殊方法的对象都被当作是函数。比如下面的例子:

python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异
class SampleMore(object):
def __call__(self, a):
return a + 5
add = SampleMore()   # A function object
print(add(2)) # Call function
map(add, [2, 4, 5]) # Pass around function object
python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

add为SampleMore类的一个对象,当被调用时,add执行加5的操作。add还可以作为函数对象,被传递给map()函数。

当然,我们还可以使用更“优美”的方式,想想是什么。

总结

对于内置的对象来说(比如整数、表、字符串等),它们所需要的特殊方法都已经在Python中准备好了。而用户自己定义的对象也可以通过增加特殊方法,来实现自定义的语法。特殊方法比较靠近Python的底层,许多Python功能的实现都要依赖于特殊方法。我们将在以后看到更多的例子。

python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

大黄蜂,还是Camaro跑车

Python的许多语法都是基于其面向对象模型的封装。对象模型是Python的骨架,是功能完备、火力强大的大黄蜂。但是Python也提供更加简洁的语法,让你使用不同的编程形态,从而在必要时隐藏一些面向对象的接口。正如我们看到的Camaro跑车,将自己威风的火药库收起来,提供方便人类使用的车门和座椅。

3.对象的属性

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

Python一切皆对象(object),每个对象都可能有多个属性(attribute)。Python的属性有一套统一的管理方案。

属性的__dict__系统

对象的属性可能来自于其类定义,叫做类属性(class attribute)。类属性可能来自类定义自身,也可能根据类定义继承来的。一个对象的属性还可能是该对象实例定义的,叫做对象属性(object attribute)。

对象的属性储存在对象的__dict__属性中。__dict__为一个词典,键为属性名,对应的值为属性本身。我们看下面的类和对象。chicken类继承自bird类,而summer为chicken类的一个对象。

python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异
class bird(object):
feather = True class chicken(bird):
fly = False
def __init__(self, age):
self.age = age summer = chicken(2) print(bird.__dict__)
print(chicken.__dict__)
print(summer.__dict__)
python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

下面为我们的输出结果:

{'__dict__': <attribute '__dict__' of 'bird' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'bird' objects>, 'feather': True, '__doc__': None}

{'fly': False, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0x2b91db476d70>}

{'age': 2}

第一行为bird类的属性,比如feather。第二行为chicken类的属性,比如fly和__init__方法。第三行为summer对象的属性,也就是age。有一些属性,比如__doc__,并不是由我们定义的,而是由Python自动生成。此外,bird类也有父类,是object类(正如我们的bird定义,class bird(object))。这个object类是Python中所有类的父类。

可以看到,Python中的属性是分层定义的,比如这里分为object/bird/chicken/summer这四层。当我们需要调用某个属性的时候,Python会一层层向上遍历,直到找到那个属性。(某个属性可能出现再不同的层被重复定义,Python向上的过程中,会选取先遇到的那一个,也就是比较低层的属性定义)。

当我们有一个summer对象的时候,分别查询summer对象、chicken类、bird类以及object类的属性,就可以知道summer对象所有的__dict__,就可以找到通过对象summer可以调用和修改的所有属性了。下面两种属性修改方法等效:

summer.__dict__['age'] = 3
print(summer.__dict__['age']) summer.age = 5
print(summer.age)

(上面的情况中,我们已经知道了summer对象的类为chicken,而chicken类的父类为bird。如果只有一个对象,而不知道它的类以及其他信息的时候,我们可以利用__class__属性找到对象的类,然后调用类的__base__属性来查询父类)

特性

同一个对象的不同属性之间可能存在依赖关系。当某个属性被修改时,我们希望依赖于该属性的其他属性也同时变化。这时,我们不能通过__dict__的方式来静态的储存属性。Python提供了多种即时生成属性的方法。其中一种称为特性(property)。特性是特殊的属性。比如我们为chicken类增加一个特性adult。当对象的age超过1时,adult为True;否则为False:

python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异
class bird(object):
feather = True class chicken(bird):
fly = False
def __init__(self, age):
self.age = age
def getAdult(self):
if self.age > 1.0: return True
else: return False
adult = property(getAdult) # property is built-in summer = chicken(2) print(summer.adult)
summer.age = 0.5
print(summer.adult)
python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

特性使用内置函数property()来创建。property()最多可以加载四个参数。前三个参数为函数,分别用于处理查询特性、修改特性、删除特性。最后一个参数为特性的文档,可以为一个字符串,起说明作用。

我们使用下面一个例子进一步说明:

python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异
class num(object):
def __init__(self, value):
self.value = value
def getNeg(self):
return -self.value
def setNeg(self, value):
self.value = -value
def delNeg(self):
print("value also deleted")
del self.value
neg = property(getNeg, setNeg, delNeg, "I'm negative") x = num(1.1)
print(x.neg)
x.neg = -22
print(x.value)
print(num.neg.__doc__)
del x.neg
python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

上面的num为一个数字,而neg为一个特性,用来表示数字的负数。当一个数字确定的时候,它的负数总是确定的;而当我们修改一个数的负数时,它本身的值也应该变化。这两点由getNeg和setNeg来实现。而delNeg表示的是,如果删除特性neg,那么应该执行的操作是删除属性value。property()的最后一个参数("I'm negative")为特性negative的说明文档。

使用特殊方法__getattr__

我们可以用__getattr__(self, name)来查询即时生成的属性。当我们查询一个属性时,如果通过__dict__方法无法找到该属性,那么Python会调用对象的__getattr__方法,来即时生成该属性。比如:

python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异
class bird(object):
feather = True class chicken(bird):
fly = False
def __init__(self, age):
self.age = age
def __getattr__(self, name):
if name == 'adult':
if self.age > 1.0: return True
else: return False
else: raise AttributeError(name) summer = chicken(2) print(summer.adult)
summer.age = 0.5
print(summer.adult) print(summer.male)
python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

每个特性需要有自己的处理函数,而__getattr__可以将所有的即时生成属性放在同一个函数中处理。__getattr__可以根据函数名区别处理不同的属性。比如上面我们查询属性名male的时候,raise AttributeError。

(Python中还有一个__getattribute__特殊方法,用于查询任意属性。__getattr__只能用来查询不在__dict__系统中的属性)

__setattr__(self, name, value)和__delattr__(self, name)可用于修改和删除属性。它们的应用面更广,可用于任意属性。

即时生成属性的其他方式

即时生成属性还可以使用其他的方式,比如descriptor(descriptor类实际上是property()函数的底层,property()实际上创建了一个该类的对象)。有兴趣可以进一步查阅。

总结

__dict__分层存储属性。每一层的__dict__只存储该层新增的属性。子类不需要重复存储父类中的属性。

即时生成属性是值得了解的概念。在Python开发中,你有可能使用这种方法来更合理的管理对象的属性。

#2楼 2012-12-12 08:55 zhuangzhuang1988 
不错不错不错,很好很好
查了下还有这种方法
1
2
3
4
5
6
7
class C(object):
    @property
    def x(self): return self._x
    @x.setter
    def x(self, value): self._x = value
    @x.deleter
    def x(self): del self._x

#3楼[楼主] 2012-12-12 09:13 Vamei 

@zhuangzhuang1988
这个就是一种property的用法,只是用了decorator.
#1楼 2012-11-20 09:41 Chenkun 
写的很好,支持一下!
另外:楼主用的是哪个版本的python, 文中的属性小节中__getattr__应该是__getattribute__吧, 通常我们使用的时候会封装一下, 如下:
1
2
3
class student(object):
    def __getattr__(self, name):
        return object.__getattribute__(name)
#2楼 2012-11-20 10:21 zhuangzhuang1988 
@Chenkun
我也是的 没看到..
#3楼 2012-11-20 10:33 Chenkun 
@zhuangzhuang1988
我又去翻了一下官方的文档, 在3.4.2. Customizing attribute access 节中有对__getattr__的解释 :)
#4楼[楼主] 2012-11-20 10:42 Vamei 
@Chenkun
我把它想简单了。等我再读一下官方文档看怎么修改,谢谢。
#5楼[楼主] 2012-11-20 10:59 Vamei 
@Chenkun
@zhuangzhuang1988
我的原意是用__dict__,但是由于还没有提到过__dict__,所以用了__getattr__,但看来是用错了。

整个过程是先用__dict__搜查属性,如果没有,向上找__base__的属性。如果整个树的没有,那么调用__getattr__来生成属性。所以说__dict__和__getattr__是相互配合工作的关系。
而__getattribute__则是无条件的返回属性,无论这一属性是否存在,所以比较“暴力”。

我参考了
http://*.com/questions/4295678/understanding-the-difference-between-getattr-and-getattribute

谢谢你们的提醒。