1. 实例属性
在定义 Person 类时,可以为Person类添加一个特殊的__init__()方法,当创建实例时,init()方法被自动调用,我们就能在此为每个实例都统一加上以下属性。
class Person(object):
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
init() 方法的第一个参数必须是 self(也可以用别的名字,但建议使用习惯用法),后续参数则可以自由指定,和定义函数没有任何区别。
xiaoming = Person('Xiao Ming', 'boy', 13)
xiaohong = Person('Xiao Hong', 'girl', 14)
print(xiaohong.name)
print(xiaohong.sex)
print(xiaohong.age)
# 但当访问不存在的属性时,会报错
print(xiaohong.birth)
2. 类属性
类和实例对象是有区别的,类是抽象,是模板,而实例则是根据类创建的对象,比如类:动物,只是一个抽象,并没有动物的详细信息,而猫、狗等,则是具体的动物,是类的对象。
在前面,实例对象绑定的属性只属于这个实例,绑定在一个实例上的属性不会影响其它实例;同样的,类也可以绑定属性,但是类的属性不属于任何一个对象,而是属于这个类。如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且,所有实例访问的类属性都是同一个!也就是说,实例属性每个实例各自拥有,互相独立,而类属性有且只有一份。
class Animal(object):
'''
localtion就是属于Animal这个类的类属性,此后,通过Animal()实例化的所有对象,
都可以访问到localtion,并且得到唯一的结果。
'''
localtion = 'Asia'
def __init__(self, name, age):
self.name = name
self.age = age
dog = Animal('wangwang', 1)
cat = Animal('mimi', 3)
print(dog.localtion) # ==> Asia
print(cat.localtion) # ==> Asia
# 类属性,也可以通过类名直接访问
print(Animal.localtion) # ==> Asia
'''
类属性也是可以动态添加和修改的,需要注意的是,因为类属性只有一份,
所以改变了,所有实例可以访问到的类属性都会变更:
'''
Animal.localtion = 'Africa'
print(cat.localtion) # ==>Africa
print(dog.localtion) # ==>Africa
# demo
class Animal():
count = 0
def __init__(self,name,age):
self.name = name
self.age = age
Animal.count += 1
dog = Animal("wangwang",1)
print(dog.count)
cat = Animal("mimi",2)
print(cat.count)
pig = Animal("panpan",3)
print(pig.count)
3. 类属性和实例属性的优先级
属性可以分为类属性和实例属性,那么问题就来了,如果类属性和实例属性名字相同时,会怎么样?
class Animal(object):
localtion = 'Asia'
def __init__(self, name, age, localtion):
self.name = name
self.age = age
self.localtion = localtion
dog = Animal('wangwang', 1, 'GuangDong')
cat = Animal('mimi', 3, 'ChongQing')
print(dog.localtion) # ==> GuangDong
print(cat.localtion) # ==> ChongQing
print(Animal.localtion) # ==> Asia
可见,在类属性和实例属性同时存在的情况下,实例属性的优先级是要高于类属性的,在操作实例的时候,优先是操作实例的属性。另外,当实例没有和类同名的时候,通过实例对象,依然可以访问到类属性。
class Animal(object):
localtion = 'Asia'
def __init__(self, name, age):
self.name = name
self.age = age
cat = Animal('mimi', 3)
print(cat.localtion) # ==> Asia
# 但是不能将其修改,下面的操作只是新增了实例属性
cat.localtion = 'Africa'
print(Animal.localtion) # ==> Asia
print(cat.localtion) # ==> Africa
4. 私有属性
并不是所有的属性都可以被外部访问的,不能被外部访问的属性称为私有属性。私有属性是以双下划线’__'开头。
在外部访问私有属性将会抛出异常,提示没有这个属性。虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。私有属性是为了保护类或实例属性不被外部污染而设计的。
# 类私有属性
class Animal(object):
__localtion = 'Asia'
# 直接访问类私有属性报错
print(Animal.__localtion)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Animal' has no attribute '__localtion'
# 实例私有属性
class Animal(object):
def __init__(self, name, age, localtion):
self.name = name
self.age = age
self.__localtion = localtion
dog = Animal('wangwang', 1, 'GuangDong')
print(dog.name) # ==> wangwang
print(dog.age) # ==> 1
# 直接访问实例私有属性报错
print(dog.__localtion)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Animal' object has no attribute '__localtion'
5. 实例方法
上面提到,私有属性没有办法从外部访问,只能在类的内部操作;那如果外部需要操作私有属性怎么办?这个时候可以通过定义类或者实例的方法来操作私有属性,实例的方法指的就是在类中定义的函数,实例方法的第一个参数永远都是self,self是一个引用,指向调用该方法的实例对象本身,除此以外,其他参数和普通函数是完全一样的。,而下面的get_name(self) 就是一个实例方法,在实例方法里面是可以操作私有属性的。另外,init(self, name)其实也可看做是一个特殊的实例方法。通过定义get_name(self)方法,在外部就可以通过这个方法访问私有属性了。
class Person(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
p = Person('Alice')
print(p.get_name()) # ==> Alice
在外部调用实例方法时,是不需要显式传递self参数的。另外,通过定义实例方法来操作私有属性的这种方法是推荐的,这种数据封装的形式除了能保护内部数据一致性外,还可以简化外部调用的难度。当然,实例方法并不仅仅是为私有属性服务的,我们可以把和类的实例有关的操作都抽象成实例方法,比如:打印实例的详细信息等等。
class Animal(object):
def __init__(self, name, age, localtion):
self.name = name
self.age = age
self.localtion = localtion
def get_info(self):
return 'name = {}, age = {}, localtion = {}'.format(self.name, self.age, self.localtion)
dog = Animal('wangwang', 1, 'GuangDong')
print(dog.get_info())
6. 类方法
在上面,为了操作实例对象的私有属性,我们定义了实例方法;同样的,如果需要需要操作类的私有属性,则应该定义类的方法。默认的,在Class中定义的全部是实例方法,实例方法第一个参数 self 是实例本身。要在class中定义类方法,需要这么写:
# demo1
class Animal(object):
__localtion = 'Asia'
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def set_localtion(cls, localtion):
cls.__localtion = localtion
@classmethod
def get_localtion(cls):
return cls.__localtion
print(Animal.get_localtion()) # ==> Asia
Animal.set_localtion('Afica')
print(Animal.get_localtion()) # ==> Africa
# demo2
class Animal(object):
__localtion = 'Asia'
__count = 0
def __init__(self, name, age):
self.name = name
self.age = age
Animal.__count += 1
@classmethod
def get_count(cls):
return cls.__count
dog = Animal('wangwang', 1)
cat = Animal('mimi', 3)
pig = Animal('panpan', 1)
count = Animal.get_count()
print(count)
和实例方法不同的是,这里有几点需要特别注意:
- 类方法需要使用@classmethod来标记为类方法,否则定义的还是实例方法
- 类方法的第一个参数将传入类本身,通常将参数名命名为 cls,上面的 cls.__localtion 实际上相当于Animal.__localtion。
- 因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用
7. 类继承
7.1 单继承
对人类的抽象可以定义为Person类,而学生、老师等,也都是人类,所以如果定义学生Student的类,可以继承Person类
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
student = Student('Alice', 'girl', 100)
print(student.name) # ==> Alice
print(student.gender) # ==> girl
print(student.score) # ==> 100
在定义继承类的时候,有几点是需要注意的:
- class Student()定义的时候,需要在括号内写明继承的类Person
- 在__init__()方法,需要调用super(Student, self).init(name, gender),来初始化从父类继承过来的属性
7.2 多重继承
除了从一个父类继承外,Python允许从多个父类继承,称为多重继承。多重继承和单继承没有特别大的差异,只是在括号内加入多个需要继承的类的名字即可。
class A(object):
def __init__(self, a):
print ('init A...')
self.a = a
class B(A):
def __init__(self, a):
super(B, self).__init__(a)
print ('init B...')
class C(A):
def __init__(self, a):
super(C, self).__init__(a)
print ('init C...')
class D(B, C):
def __init__(self, a):
super(D, self).__init__(a)
print ('init D...')
实践证明,在多重继承里,A虽然被继承了两次,但是__init__()的方法只调用一次。多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
>>> d = D('d')
init A...
init C...
init B...
8. 判断类型
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
class Teacher(Person):
def __init__(self, name, gender, course):
super(Teacher, self).__init__(name, gender)
self.course = course
p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
isinstance 判断类型
>>> isinstance(p, Person)
True # p是Person类型
>>> isinstance(p, Student)
False # p不是Student类型
>>> isinstance(p, Teacher)
False # p不是Teacher类型
这说明在继承链上,一个父类的实例不能是子类类型,因为子类比父类多了一些属性和方法,再看 s:
>>> isinstance(s, Person)
True # s是Person类型
>>> isinstance(s, Student)
True # s是Student类型
>>> isinstance(s, Teacher)
False # s不是Teacher类型
s 是Student类型,不是Teacher类型,这很容易理解。但是s 也是Person类型,因为Student继承自Person,虽然它比Person多了一些属性和方法,但是,把 s 看成Person的实例也是可以的。这说明在一条继承链上,一个实例可以看成它本身的类型,也可以看成它父类的类型。
顺便提一句,isinstance也可以用于Python自有数据类型的判断。
s = 'this is a string.'
n = 10
isinstance(s, int) # ==> False
isinstance(n, str) # ==> False
9. 多态
类具有继承关系,并且子类类型可以向上转型看做父类类型,如果我们从 Person 派生出 Student和Teacher ,并都写了一个who() 方法:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def who(self):
return 'I am a Person, my name is %s' % self.name
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def who(self):
return 'I am a Student, my name is %s' % self.name
class Teacher(Person):
def __init__(self, name, gender, course):
super(Teacher, self).__init__(name, gender)
self.course = course
def who(self):
return 'I am a Teacher, my name is %s' % self.name
p = Person('Tim', 'Male') # ==> I am a Person, my name is Tim
s = Student('Bob', 'Male', 88) # ==> I am a Student, my name is Bob
t = Teacher('Alice', 'Female', 'English') # ⇒ I am a Teacher, my name is Alice
从定义上来讲,Student和Teacher都拥有来自父类Person继承的who()方法,以及自己定义的who()方法。但是在实际调用的时候,会首先查找自身的定义,如果自身有定义,则优先使用自己定义的函数;如果没有定义,则顺着继承链向上找。
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def who(self):
return 'I am a Person, my name is %s' % self.name
class Boss(Person):
def __init__(self, name, gender,company):
super(Boss, self).__init__(name, gender)
self.company = company
b = Boss('Bob', 'Male', 'Alibaba')
# 在Boss的定义类,没有定义who方法,所以会顺着继承链向上找到父类的who方法并且调用
b.who() # ==> I am a Person, my name is Bob
10. 获取对象信息
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
p = Person('Alice', 'Female')
s = Student('Bob', 'Male', 100)
type(p) # ==> <class '__main__.Person'>
type(s) # ==> <class '__main__.Student'>
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def who(self):
return 'I am a Person, my name is {}'.format(self.name)
p = Person('Alice', 'Female')
dir(p)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'gender', 'name', 'who']
dir()返回所有实例属性,包括__class__这类有特殊意义的属性。注意到方法who也是p的一个属性。dir()返回的属性是字符串列表,如果已知一个属性名称,要获取或者设置对象的属性,就需要用 getattr() 和 setattr( )函数了。
>>> getattr(p, 'name') # 获取name属性
'Alice'
>>> setattr(p, 'name', 'Adam') # 设置新的name属性
>>> s.name
'Adam'
>>> getattr(s, 'age') # 获取age属性,但是属性不存在,报错:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute 'age'
>>> getattr(s, 'age', 20) # 获取age属性,如果属性不存在,就返回默认值20
class Person(object):
def __init__(self, name, gender, **kw):
self.name = name
self.gender = gender
for k, v in kw.items():
setattr(self, k, v)
p = Person('Bob', 'Male', age=18, course='python')
print(p.age) # ==> 18
print(p.course) # ==> python