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

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,调用类属性。

复制代码
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()
复制代码

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

 

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

 

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

 

3.对象的属性(性质)

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

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

 

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

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

复制代码
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()
复制代码

在初始化中,将参数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__()特殊方法的对象都被当作是函数。比如下面的例子:

复制代码
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
复制代码

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

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

 

总结

对于内置的对象来说(比如整数、表、字符串等),它们所需要的特殊方法都已经在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类的一个对象。

复制代码
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__)
复制代码

 

 

下面为我们的输出结果:

{'__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:

复制代码
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)
复制代码

 

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

 

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

复制代码
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
复制代码

 

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

 

使用特殊方法__getattr__

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

复制代码
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)
复制代码

 

 

每个特性需要有自己的处理函数,而__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://stackoverflow.com/questions/4295678/understanding-the-difference-between-getattr-and-getattribute

谢谢你们的提醒。

转载于:https://www.cnblogs.com/cnshen/p/3682082.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值