• 类的定义

与c++、java类似,在Python中使用class关键字来定义一个类:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

class后面接着的是类名,类名通常首字母大写;类名之后是继承的父类,Python中允许一个类继承多个父类,父类之间使用逗号,隔开,并用括号括起来。如果没有合适的继承类,那么使用object类,object是所有类最终的父类。

若需要在创建实例时绑定一些属性,那么需要在类的定义中定义一个__init__(前后两个下划线)方法,类似于c++中的构造函数。和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量selfself指向该类的具体实例,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不要使用类似__name__、__score__这样的变量名;有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”;双下划线开头的实例变量也不是一定不能从外部访问,不能直接访问类似__name这样的变量是因为Python解释器对外把__name变量改成了_[类名]__name,所以,仍然可以通过_[类名]__name来访问__name变量,但是不同版本的Python解释器可能会把__name改成不同的变量名,所以强烈不建议这样干。

  • 获取对象信息

要判断一个对象的类型,首先可以使用type()函数:

type(123)  #<class 'int'>
type('str')  #<class 'str'>
type(None)  #<type(None) 'NoneType'>

若要使用if进行判断,可以将两个对象的type类型进行比较,或者使用types模块中定义的常量:

type(123)==type(456)  #True
type(123)==int  #True
type('abc')==str  #True
type('abc')==type(123)  #False
import types
def fn():
    pass
type(fn)==types.FunctionType  #True
type(abs)==types.BuiltinFunctionType  #True
type(lambda x: x)==types.LambdaType  #True
type((x for x in range(10)))==types.GeneratorType  #True

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。如:

class Animal(object):
    ...

class Dog(Animal):
    ...

a=Animal()
d=Dog()
isinstance(d,Dog)  #True
isinstance(d,Animal)  #True
isinstance(a,Dog)  #False

使用isinstance()还可以判断一个变量是否是某些类型中的一种,如:

isinstance([1, 2, 3], (list, tuple))  #True
isinstance((1, 2, 3), (list, tuple))  #True

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

dir('ABC')
#['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,对于我们自己写的类,如果也想用len(myObj)的话,可以自己写一个__len__()方法。配合getattr()setattr()以及hasattr(),我们还可以直接操作一个对象的状态:

class MyObject(object):
    def __init__(self):
        self.x = 9
    def power(self):
        return self.x * self.x

obj = MyObject()

hasattr(obj, 'x') # 有属性'x'吗?
#True
print(obj.x)  #9
hasattr(obj, 'y') # 有属性'y'吗?
#False
setattr(obj, 'y', 19) # 设置一个属性'y'
hasattr(obj, 'y') # 有属性'y'吗?
#True
print(getattr(obj, 'y')) # 获取属性'y'
#19
print(obj.y) # 获取属性'y'
#19

如果试图获取不存在的属性,会抛出AttributeError的错误。使用getattr()也可以获取对象的方法:

hasattr(obj, 'power') # 有属性'power'吗?
#True
print(getattr(obj, 'power')) # 获取属性'power'
#<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
print(fn) # fn指向obj.power
#<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
print(fn()) # 调用fn()与调用obj.power()是一样的
#81
  • 实例属性和类属性

有这样一个例子:

class AAA(): 
    aaa = 10
# 情形1 
obj1 = AAA() 
obj2 = AAA() 
print obj1.aaa, obj2.aaa, AAA.aaa
# 情形2 
obj1.aaa += 2 
print obj1.aaa, obj2.aaa, AAA.aaa
# 情形3 
AAA.aaa += 3 
print obj1.aaa, obj2.aaa, AAA.aaa
#情形1的结果是:10 10 10;
#情形2的结果是:12 10 10;
#情形3的结果是:12 13 13

情形1和情形2的结果好理解,为什么情形3输出的三个结果不同呢?可以这样理解,类的实例会继承类的所有属性和方法,类AAA中有属性aaa=10,因此obj1和obj2都继承了属性aaa,但是obj1和obj2并不是“真正”有这个属性,在输出obj1.aaa和obj2.aaa时实际是输出AAA.aaa,这也就是情形1的结果;在情形2中obj1.aaa+=2实际上是obj1.aaa=obj1.aaa+2,此时给obj1定义了一个名叫aaa的属性,由于与父类的属性aaa同名,因此obj1.aaa屏蔽了父类的aaa,输出obj1.aaa时输出的是实例obj1自己定义的aaa属性,与父类的aaa属性无关;因为obj2并没有定义aaa属性,所以输出obj2.aaa时输出的还是AAA.aaa,这就是情形3的结果。

从上面的例子可以看出,在编写程序的时候,不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。