python-类简介

一.类的定义

定义方式:class关键字
例如,下面创建了一个Person的类,并且实现了这个类的初始化函数"init":

#coding=UTF-8
class Person(object):
    count = 0
    books = ["python"]
    def __init__(self, name, age):
        self.name = name
        self.age = age

接下来就通过上面的Person类来看看Python中类的相关内容。

二.数据属性

在上面的Person类中,"count"、"books"以及"name"、"age"都被称为类的数据属性,但是它们又分为类数据属性和实例数据属性

1.类数据属性

类变量必须是紧接在类名后面定义的变量,相当于java和c++的static变量
如"count"、"books"

class Person(object):
    count = 0
    books = ["python"]
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def fcn(self, val=400):
          val=300
          self.val1 = val
          self.val2 = 500
>>Person.val
AttributeError: type object 'Person' has no attribute 'val'

这说明类变量只能是在类名后面的定义的变量,其他处的变量不是类变量

(1)类可以访问已定义的类数据属性

主要有三种访问方式:
a.

>>print Person.books
['python']

b.

class Person(object):
    count = 0
    books = ["python"]
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print self.__class__.count  #__class__:类对象的类型
>>Mary=Person("Mary",12)
0

c.

class Person(object):
    count = 0
    books = ["python"]
    def __init__(self, name, age):
        self.name = name
        self.age = age
    @classmethod
    def echo(cls):
        print cls.count
>>Person.echo()
0
(2)类可以修改已定义的类数据属性
>>Person.books.append("R")
>>print Person.books
['python', 'R']
(3)类也可以增加新的类数据属性
>>Person.hobbies = ["reading", "swimming"]
>>print Person.hobbies  
['reading', 'swimming']
#还可以用内建函数dir()或者访问类的字典属性__dict__来查看类的所有属性,可以看出是否增加了hobbies类数据属性
>>print dir(Person)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'books', 'count', 'hobbies']
>>print Person.__dict__
{'count': 0, '__module__': '__main__', 'books': ['python', 'R'], 'hobbies': ['reading', 'swimming'], '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, '__init__': <function __init__ at 0x0000000002442EB8>}

2.实例数据属性

实例变量必须在____init____函数里定义,相当于java和c++的普通变量如"name"、"age"

class Person(object):
    count = 0
    books = ["python"]
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def fcn(self, val=400):
          val=300
          self.val1 = val
          self.val2 = 500
>>Mary=Person("Mary",12)
>>Mary.val1
AttributeError: 'Person' object has no attribute 'val1'

这说明实例变量必须是init函数里以self开头的变量,其他以self开头的变量并不是实例变量

(1)类实例可以访问已定义的实例数据属性
>>Mary= Person("Mary", 28) 
>>print "%s is %d years old" %(Mary.name, Mary.age) 
Mary is 28 years old
(2)类实例可以修改已定义的实例数据属性
>>Mary.age=15
>>print Mary.age
15
(3)类实例也可以增加新的实例数据属性
>>wilber.gender = "male" 
>>print Mary.gender   
"male"
>>print dir(wilber) # 查看发现类实例Mary实例是否增加了gender实例数据属性
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'books', 'count', 'gender', 'hobbies', 'name']
# "gender" 是新增的实例属性,是仅仅属于 Mary实例的,别的实例不能访问
>>Jack= Person("Jack", 27) 
>>print Jack.gender#实例Jack引用属性gender会报错
AttributeError: 'Person' object has no attribute 'gender'

3.类数据属性和实例数据属性的联系

(1)类实例可以访问类数据属性,包括类定义中的和新增的类数据属性
>>print Mary.books,Mary.hobbies
['python', 'R'] ['reading', 'swimming']
(2)类实例可以修改类数据属性
>>Mary.books.append("C#")
>>print Mary.books
['python', 'R', 'C#']

但是,在通过实例来访问类属性的时候一定要谨慎,因为可能出现属性"隐藏"的情况。
当一个类的实例被构造时,会将当前类变量赋值给这个实例,当前类变量的值是是多少,这个实例得到的类变量的值就是多少
a.不可变类型的类数据属性
当通过实例来赋值或修改类数据属性时,相当于为实例新建一个实例属性,此时无论类如何修改此类数据属性,通过实例访问此数据属性仍是不变的,因为这个数据属性是属于实例新建的实例数据属性,而不是原来的那个类数据属性,只是名字一样而已,因此通过类访问此数据属性和通过实例访问此数据属性的值都是不同的;如果我们删除了实例的新建的实例数据属性,那么类修改类数据属性后,通过实例访问此数据属性的值也会随着类改变而改变

>>print Mary.count,Person.count
0 0
>>print "Person.count is Mary.count: ", Person.count is Mary.count
Person.count is Mary.count:  True
#通过实例来赋值count属性,相当于为实例新建了一个count属性
>>Mary.count=1
>>print Mary.count,Person.count
1 0
>>print "Person.count is Mary.count: ", Person.count is Mary.count
Person.count is Mary.count:  False
>>Person.count=3
>>print Mary.count,Person.count
1,3
#删掉实例的count属性
>>del Mary.count
>>print Mary.count,Person.count
3,3
>>print "Person.count is Mary.count: ", Person.count is Mary.count
Person.count is Mary.count:  True
#删除实例的count实例数据属性后,通过类来修改count类属性时,实例访问和通过类访问的值是一样的
>>Person.count = 1
>>print Mary.count,Person.count
1,1
>>print "Person.count is Mary.count: ", Person.count is Mary.count
Person.count is Mary.count: True

b.可变类型的类数据属性
当通过实例来赋值类数据属性时,相当于为实例新建一个实例属性,此时通过类访问此数据属性和通过实例访问此数据属性是不同的;
通过实例来修改类数据属性,实际上是修改实例的数据属性指向的内存地址,此时通过类来访问数据属性和通过实例来访问数据属性是相同

>>print Person.books,Mary.books
['python'] ['python']
>>Person.books.append("R")
>>print Person.books,Mary.books
['python', 'R'] ['python', 'R']
#通过实例来修改类数据属性books,相当于改变了Mary.books指向的内存地址即Person.books
>>Mary.books.append("scala")
>>print Person.books,Mary.books
['python', 'R', 'scala'] ['python', 'R', 'scala']
#通过实例来赋值类数据属性,相当于为实例新建了一个books属性
>>Mary.books=["S"]
>>print Person.books,Mary.books
['python', 'R', 'scala'] ['S']
>>print "Person.books is Mary.books: ", Person.books is Mary.books
Person.books is Mary.books:  False
#删掉实例的books属性
>>del Mary.books
>>print Person.books,Mary.books
['python', 'R', 'scala'] ['python', 'R', 'scala']
>>print "Person.books is Mary.books: ", Person.books is Mary.books
Person.books is Mary.books:  True
#删除实例的实例数据属性count后,又和开始一样
>>Mary.books.append("C#")
>>print Person.books,Mary.books
['python', 'R', 'scala', 'C#'] ['python', 'R', 'scala', 'C#']
>>print "Person.books is Mary.books: ", Person.books is Mary.books
Person.books is Mary.books:  True

因此,尽管通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦。

(3)类不能访问实例数据属性--无论是类定义中已有的实例数据属性还是新增的实例数据属性
>>print Person.name #类定义中的实例数据属性
AttributeError: type object 'Person' has no attribute 'name'
>>print Person.gender#实例新增的数据属性
AttributeError: type object 'Person' has no attribute 'gender'

4.总结

对于类数据属性和实例数据属性,可以总结为:
1.类数据属性属于类本身,可以通过类名进行访问、修改、增加
2.类数据属性也可以被类的所有实例访问、修改,包括已有的和新增的,即类数据属性可以被类和所有实例所共享,但是通过实例访问类数据属性要注意可能会出现属性“隐藏”的情况
3.实例数据属性属于实例本身,可以通过实例进行访问、修改、增加,但是增加的实例数据属性只属于该实例,不能被其他实例访问
4.实例数据属性只能通过实例访问,不能通过类访问

三.方法

在一个类中,可能出现三种方法,实例方法、静态方法和类方法,下面来看看三种方法的不同。

1.实例方法

(1) 实例方法的第一个参数默认为"self",这里"self"就代表这个类实例本身,通过"self"可以直接访问实例的属性 。self不是一个关键字,而是约定的写法。
(2) 实例方法只能通过类实例进行调用
将Person的定义改为以下形式:

class Person(object):
    count = 0
    books = []
    def __init__(this, name, age):
        this.name = name
        this.age = age
    def hi(this):
        print this.name
>>Mary=Person("Mary",12)#用this代替self运行依然正确,其中init()是生成实例时默认调用的实例方法
>>print Mary.age
12
>>Mary.hi()
Mary
>>Person.hi()
TypeError: unbound method hi() must be called with Person instance as first argument (got nothing instead)

2.类方法

(1)类方法以cls作为第一个参数,cls表示类本身,通过cls可以访问类的相关属性;
(2)定义时使用@classmethod装饰器
(3)类方法既可以通过类名访问,也可以通过实例访问

class Person(object):
    count = 0
    books = []
    def __init__(self, name, age):
        self.name = name
        self.age = age
    @classmethod
    def printClassInfo(cls):
        print cls.__name__
>>Person.printClassInfo()#通过类名访问
Person
>>Mary= Person("Mary", 28)
>>Mary.printClassInfo()#通过实例访问
Person

注;很多其他的编程语言不允许实例调用类方法。

3.静态方法

(1)与实例方法和类方法不同的是,静态方法没有参数限制,既不需要实例参数,也不需要类参数;
(2)定义的时候使用@staticmethod装饰器
(3)同类方法一样,静态法可以通过类名访问,也可以通过实例访问
(4)使用静态方法的好处是,不需要定义实例即可使用这个方法。另外,多个实例共享此静态方法。

class Person(object):
    count = 0
    books = ["python"]
    def __init__(self, name, age):
        self.name = name
        self.age = age
    @classmethod
    def printClassInfo(cls):
        print cls.__name__
    @staticmethod
    def printClassAttr():
        print Person.count
        print Person.books
        #print self.name 静态方法不能访问类变量和实例变量
>>Person.printClassAttr()
0
['python']
>>Mary= Person("Mary", 28)
>>Mary.printClassAttr()
0


4.总结

这三种方法的区别在于
1.实现静态方法和类方法都是依赖于python的修饰器来实现的。对象方法有self参数,类方法有cls参数,静态方法是不需要这些附加参数的。
2.类方法的隐含调用参数是类,而类实例方法的隐含调用参数是类的实例,静态方法没有隐含调用参数
3.实例方法被绑定到一个实例,只能通过实例进行调用;但是对于静态方法和类方法,可以通过类名和类实例两种方式进行调用。抛开静态方法不说,实例方法、类方法与实例数据属性、类数据属性之间的区别是一样的,即实例的可以访问类的,而类的不能访问实例的;

四.访问控制

Python中没有访问控制的关键字,例如private、protected等等。但是,在Python编码中,有一些约定来进行访问控制。

1.单下划线"_"

在Python中,通过单下划线来实现模块级别的私有化,一般约定以单下划线开头的变量、函数为模块私有的,也就是说"from moduleName import *"将不会引入以单下划线"_"开头的变量、函数。
现在有一个模块lib.py,内容如下,模块中一个变量名和一个函数名分别以单下划线开头:

a= 10
_a = 100
def print():
    print "a is:", a
    print "_a is:", _a
def _print():
    print "a is:", a
    print "_a is:", _a

当通过下面代码引入lib.py这个模块后,所有的以"_"开头的变量和函数都没有被引入,如果访问将会抛出异常:

>>from lib import *
>>print a
10
>>print()
a is: 10
_a is: 100
>>print _a
NameError: name '_a' is not defined
>>_print()
NameError: name '_print' is not defined

2.双下划线"__"

(1) Python中的类属性,可以通过双下划线"__"来实现一定程度的私有化

在Person类中,加入了一个"__address"属性:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__address = "hangzhou"
>>Mary= Person("Mary", 28)
>>print Mary.__address
AttributeError: 'Person' object has no attribute '__address'

当通过实例Mary访问这个属性的时候,就会得到一个异常,提示属性"__address"不存在。
下面我们通过内建函数dir()就可以看到原因,"__address"属性在运行时,属性名被改为了"_Person__address"(属性名前增加了单下划线和类名)

>>print dir(Mary)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'books', 'count', 'printClassAttr', 'printClassInfo']
['_Person__address', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'books', 'count', 'name', 'printClassAttr', 'printClassInfo']

所以说,即使是双下划线,也没有实现属性的完全的私有化,因为通过下面的方式还是可以直接访问"__address"属性:

>>> print Mary._Person__address
hangzhou
(2)双下划线的另一个作用是,避免子类对父类同名属性的冲突。**

看下面一个例子:

class A(object):
        def __init__(self):
                self.__private()
                self.public()
        def __private(self):
                print 'A.__private()'
        def public(self):
                print 'A.public()'
class B(A):
        def __private(self):
                print 'B.__private()'
        def public(self):
                print 'B.public()'
b = B()

在父类中定义了一个私有方法,然后在子类中定义了一个一样的私有方法是无效的,子类是不能重写父类中的私有方法的
当实例化B的时候,由于没有定义init函数,将调用父类的init,但是由于双下划线的"混淆"效果,"self.__private()"将变成 "self._A__private()"

3.总结

单下划线和双下划线的使用更多的是一种规范/约定,并没有真正达到限制的目的:
单下划线开头的表示的是protected类型的变量,即只能允许其本身与子类进行访问;同时表示弱内部变量标示,即当使用"from moduleNmae import *"时,不会将以一个下划线开头的对象引入
双下划线的表示的是私有类型的变量,只能是允许这个类本身进行访问,连子类也不可以访问,这类属性在运行时属性名前面会加上单下划线和类名。

五.总括

本文介绍了Python中类的一些基本点:
1.实例数据属性和类数据属性的特点和区别
2.实例方法,类方法和静态方法的特点和区别
3.Python中通过单下划线和双下划线实现的访问控制

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,507评论 6 13
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,673评论 1 10
  • 掌握运用《秘密》吸引力法则,你就会拥有极致的未来!! 1要求(大胆开口要求) 2信念(相信已经拥有) 3接受(满心...
    快乐雪天使阅读 310评论 0 1
  • 一个在装逼路上越走越远的程序猿。 老规矩上工作环境照片吧 这是在家的工作环境。普通的不能在普通了(屌丝标配),用了...
    _小河马阅读 3,823评论 78 52