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中通过单下划线和双下划线实现的访问控制

推荐阅读更多精彩内容