面向对象编程(Object Oriented Programming)
,简称OOP,是一种程序设计思想
,OOP把对象
当作程序的基本单元,一个对象包含了数据和操作数据的函数
一系列消息在各对象之间进行传递
,和面向对象不同,面向过程的程序设计
把计算机程序当作一系列的命令集合,即一组函数的顺序执行
,为了简化程序设计,面向过程把函数继续切分为子函数,从而降低系统的复杂度对象数据类型
就是面向对象中类(class)
的概念,下面来看一个案例来说明面向对象和面向过程的区别:- 现在我们需要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以使用一个字典表示,例如:
# -*- coding: utf-8 -*-
std_1 = {'name':'zhangsan','score':98}
std_2 = {'name':'lisi','score':97}
def print_score(std):
return '%s : %s' % (std['name'],std['score'])
print(print_score(std_1))
print(print_score(std_2))
#输出:
zhangsan : 98
lisi : 97
- 如果使用面向对象的程序设计思想,首先思考的不是程序的执行流程,而是'学生'的这种数据类型应该被看作一个'对象',这个对象有'name'和'score'两种'属性(property)',如果想要输出一个学生的成绩,首先就需要先创建一个'学生'对应的对象,然后给这个'学生'对象发送一个'打印成绩'的'消息',让对象自己把指定的数据打印出来,例如:
# -*- coding: utf-8 -*-
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))
- 给对象发送消息实际就是调用对象对应的'关联函数',这个关联函数也叫做'对象的方法',下面就是面向对象的程序调用
zhangsan = Student('zhangsan',98)
lisi = Student('lisi',97)
zhangsan.print_score()
lisi.print_score()
#输出:
zhangsan : 98
lisi : 97
对象(class)
是一种抽象的概念,上面定义的对象Student
,指的就是学生
这个概念,而实例(instance)
则指一个个具体的对象,例如上面的zhangsan
和lisi
就是两个具体的Student
,也就是实例对象(class)
,然后根据对象创建实例(instance)
类(class)
和实例(instance)
,类是抽象的模板,比如上面的Student类
,而实例是根据类创建出来的具体的对象,每个对象都有相同的方法,但是各自的数据可能不同,例如上面的zhangsan
和lisi
Student
类为例:# -*- coding: utf-8 -*- class Student(object): pass
(1)先看第2行
class Student(object): 在Python中,类是通过'class'关键字进行定义的,'class'后面跟着的是类名,类名通常是以大写字母开头的,紧接着就是'(object)',这个表示的是'Student'类是从'object'类继承下来的,继承这个概念在后面会说 通常如果说没有合适的继承类,那么就可以直接使用'object'类,这是所有的类最终都会继承的类
(2)定义好了
Student
类,就可以根据Student
类创建出Student
的实例,而创建实例是通过类名()
实现的,例如:>>> class Student(object): ... pass ... >>> zhangsan = Student() >>> zhangsan <__main__.Student object at 0x0000018C3E6A6A10> >>> Student <class '__main__.Student'> 可以看到,变量'zhangsan'指向的是'Student'实例,输出的信息中,'object at'后面是内存地址,每个object的地址都不一样,而'Student'本身就是一个类 还可以给一个实例变量自由的绑定属性,例如: >>> zhangsan.name = 'zhangsan' >>> zhangsan.name 'zhangsan'
(3)由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些必要的属性写进入,通过一个特殊的
__init__
方法,在创建实例的时候,把name
和score
等属性绑定,例如:>>> class Student(object): ... def __init__(self,name,score): ... self.name = name ... self.score = score ... >>> zhangsan = Student('zhangsan',98) >>> zhangsan.name 'zhangsan' >>> zhangsan.score 98 >>> lisi = Student() #传入空参数 Traceback (most recent call last): File "
" , line 1, in <module> TypeError: Student.__init__() missing 2 required positional arguments: 'name' and 'score' >>> lisi = Student('lisi',97,22) #多传入一个参数 Traceback (most recent call last): File "" , line 1, in <module> TypeError: Student.__init__() takes 3 positional arguments but 4 were given #注意特殊方法__init__是两个_ 可以看到在定义特殊方法'__init__'时,后面的第一个参数是'self',而且也必须是'self',这个参数表示创建的实例本身,因此,在'__init__'方法内部,就可以把各种属性绑定到'self',因为'self'就指向创建的实例本身 不过,在有了'__init__'方法之后,在创建实例的时候,就不能传入空参数或多个参数,必须传入和'__init__'方法相匹配的参数,但是'self'参数不需要传,Python解释器会自己把'实例变量'传入到self(4)和普通函数相比,在类中定义的函数只有一点不同,那就是函数的第一个参数永远都是
self
,并且在调用该函数时,不用传递参数,除此之外,和普通函数没有其他区别,仍然可以使用默认参数、可变参数、关键字参数、命名关键字参数
面向对面编程的一个重要的特点就是数据封装,在上面的Student
类中,根据对象创建的实例
里,都有各自的name
和score
的数据,可以通过函数来访问这些数据,例如:
>>> class Student(object):
... def __init__(self,name,score):
... self.name = name
... self.score = score
...
>>> zhangsan = Student('zhangsan',98)
>>> zhangsan.name
'zhangsan'
>>> zhangsan.score
98
>>> def print_score(std):
... return '%s : %s' % (std.name,std.score)
...
>>> print_score(zhangsan)
'zhangsan : 98'
但是,既然Student
实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student
类的内部定义访问数据的函数,这样,就可以把数据
封装起来了,这些封装数据的函数和Student
类本身是关联起来的,我们称之为类的方法,更改后可以这样写:
>>> class Student(object):
... def __init__(self,name,score):
... self.name = name
... self.score = score
... def print_score(self):
... return '%s : %s' % (self.name,self.score)
...
>>> zhangsan = Student('zhangsan',98)
>>> zhangsan.print_score()
'zhangsan : 98'
可以发现,'zhangsan'可以直接引用'Student'类中的'print_score'函数
同样的'print_score'函数的参数也是'self',也是不用传递的,直接在实例变量上调用即可
如果有第三个参数age,但是在__init__中并没有定义,可以这样写:
# -*- coding: utf-8 -*-
class Student(object):
def __init__(self,name,score):
self.name = name
self.score = score
def print_score(self):
return print('%s %s %s' % (self.name,self.score,self.age))
zhangsan = Student('zhangsan',98)
zhangsan.age = 22 #定义实例变量age
zhangsan.print_score()
#输出:
zhangsan 98 22
从上面可以看出,在根据Student
类创建实例时,只需要指定name
和score
的值即可,关于如何打印出来,这些都是在Student
类的内部定义的,从而使这些数据和逻辑被封装
起来,调用时并不知道内部的细节
封装的另一个好处就是可以给Student
类增加新的方法,例如:
# -*- coding: utf-8 -*-
class Student(object):
def __init__(self,name,score):
self.name = name
self.score = score
def print_score(self):
return print('%s : %s' % (self.name,self.score))
def get_grade(self):
if self.score >= 90 and self.score <= 100:
return print('A')
elif self.score >= 80:
return print('B')
else:
return print('C')
zhangsan = Student('zhangsan',98)
zhangsan.print_score()
zhangsan.get_grade()
#输出:
zhangsan : 98
A
总结:
类是创建实例的模板
,而实例是一个个具体的对象,每个实例拥有的数据都是相互独立
的,互不影响
方法
就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据
通过在实例上调用方法,其实就是
直接操作了对象内部的数据
,并且无需指定方法内部的实现细节和静态语音不同,Python允许对
实例变量绑定任何数据
,这样的效果就是,就算是根据同一类创建出的实例,实例拥有的变量名称可能都是不一样的,例如:# -*- coding: utf-8 -*- class Student(object): def __init__(self,name,score): self.name = name self.score = score zhangsan = Student('zhangsan',98) lisi = Student('lisi',98) zhangsan.age = 22 lisi.aaa = 333 print(zhangsan.age) print(lisi.aaa) print(zhangsan.aaa) #输出 22 333 Traceback (most recent call last): File "d:\工作\work\py\test02.py", line 15, in <module> print(zhangsan.aaa) AttributeError: 'Student' object has no attribute 'aaa' 因为可以绑定任何数据,所有说'zhangsan'拥有name,score,age三个变量,而'lisi'则拥有name,score,aaa三个变量,因为是实例是相互独立的,所有'zhangsan'和'lisi'之间的变量数量、变量名等都不是互通的,所以zhangsan在最后调用aaa变量时报错了
在类(class)
中,可以有属性和方法,而外部代码可以通过调用实例变量
的方法来操作或获取数据,从而隐藏了内部的复杂逻辑
但是,从上面Student类
的定义来看,外部代码可以随意改变一个实例的属性,例如:
>>> class Student(object):
... def __init__(self,name,score):
... self.name = name
... self.score = score
... def print_score(self):
... return print('%s : %s' % (self.name,self.score))
...
>>> zhangsan = Student('zhangsan',98)
>>> zhangsan.print_score()
zhangsan : 98
>>> zhangsan.score = 22 #修改score的值
>>> zhangsan.print_score() #再次调用,发现值已经变了
zhangsan : 22
可以看到实例属性的值是可以随意修改的,如果想要实例的内部属性不被外部修改,可以这样做:
#可以在属性的名称前面加"__",在Python中,实例的变量名如果以__开头的话,那么这个变量就成了私有变量,只有内部可以访问,外部无法访问
>>> class Student(object):
... def __init__(self,name,score):
... self.__name = name
... self.__score = score
... def print_score(self):
... return print('%s : %s' % (self.__name,self.__score))
...
>>> zhangsan = Student('zhangsan',98)
>>> zhangsan.print_score()
zhangsan : 98
#现在想对Student类中的score属性的值进行修改,再调用print_score方法时,发现并没有修改,最后发现,其实这相当于是新创建了一个score属性
>>> zhangsan.score = 22
>>> zhangsan.print_score()
zhangsan : 98
>>> zhangsan.score
22
#上面不行的话,有的人可能会说是因为属性名称不一样,那么现在来调用一下__score属性,发现也无法调用,这是因为在使用私有变量后,Python解释器就把__name的对外名称变成了_Student__score,使用这样的格式进行调用,是可以调用成功的,不过强烈建议不要使用这种方法进行修改、调用属性数据
>>> zhangsan.__score
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: 'Student' object has no attribute '__score'. Did you mean: 'score'?
>>> zhangsan._Student__score
98
#在不使用_Student__score这种格式去访问、修改指定的属性时,可以修改一下类,例如
>>> class Student(object):
... def __init__(self,name,score):
... self.__name = name
... self.__score = score
... def print_score(self):
... return print('%s : %s' % (self.__name,self.__score))
... def set_name(self,name):
... self.__name = name
... def set_score(self,score):
... self.__score = score
... def get_name(self):
... return print(self.__name)
... def get_score(self):
... return print(self.__score)
...
>>> zhangsan = Student('zhangsan',98)
>>> zhangsan.print_score()
zhangsan : 98
>>> zhangsan.get_name()
zhangsan
>>> zhangsan.set_name('lisi')
>>> zhangsan.get_name()
lisi
#虽然原先的"zhangsan.name = 98"也可以之间进行修改,但是通过在类中添加方法可以进行参数控制,例如:
>>> class Student(object):
... def __init__(self,name,score):
... self.__name = name
... self.__score = score
... def set_score(self,score):
... if score >= 90 and score <=100:
... self.__score = score
... else:
... return print('error')
... def get_score(self):
... return self.__score
...
>>> zhangsan = Student('zhangsan',98)
>>> zhangsan.get_score()
98
>>> zhangsan.set_score(80)
error
>>> zhangsan.get_score()
98
>>> zhangsan.set_score(96)
>>> zhangsan.get_score()
96
可以看到在'Student'类中,'set_score'方法添加了参数控制
注意:
- 在Python中,变量名称类似于
__xx__
这样的以双下划线开头和结尾的是特殊变量,特殊变量是可以直接访问的,并不是私有变量- 以一个下划线开头的,类似于
_name
这样的变量,是可以被外部访问的,但是在看到这样的变量时,要把它当成私有变量,不要随意访问,这也是一个不成文的规定- 根据拥有私有变量的类去创建的实例,在调用时,不要直接使用类似于
zhangsan.__score
这样的,因为虽然在类中定义的是__score
,但其实Python解释器对外的名称是_Student__score
,所以直接调用或修改zhangsan.__score
,其实是调用的是另外一个变量
在上面的内容中,有说到过继承
这一概念,例如:
class Student(object)
在这里Student(object)
的object
就是Student
继承的类,也就是说,我们在定义类时,是可以继承
现有的类的,创建的新类叫做子类(subclass)
,而被继承的类叫做父类
,也可以叫做基类、超类
下面来看几个案例:
- 定义一个'Animal'类,添加一个'run'方法
>>> class Animal(object):
... def run(self):
... return print('Animal is running!!!')
...
- 定义'Dog'和'Cat'类,两个类都继承'Animal'类
>>> class Dog(Animal):
... pass
...
>>> class Cat(Animal):
... pass
...
- 创建'Dog'和'Cat'的实例,发现创建后的实例是可以直接使用'Animal'的方法的
>>> dog = Dog()
>>> dog.run()
Animal is running!!!
>>> cat = Cat()
>>> cat.run()
Animal is running!!!
对于上面的案例来说,Dog
和Cat
就是Animal
的子类,Animal
是Dog
和Cat
的父类
可以看到在Dog
和Cat
继承Animal
后,是可以直接使用Animal
中的方法的,这也是继承最大的好处:子类可以获得父类的全部功能
当然,也可以对子类修改或增加一些方法,例如:
>>> class Dog(Animal):
... def run(self):
... return print('Dog is running!!!')
... def eat(self):
... return print('Dog is Eating meat')
...
- 需要注意的是,在修改完'Dog'类之后,直接调用'dog.run'变量,值还是原来的,需要重新创建实例
>>> dog.run()
Animal is running!!!
>>> dog = Dog()
>>> dog.run()
Dog is running!!!
>>> dog.eat()
Dog is Eating meat
在对子类添加、修改方法后,可以看到创建实例并调用时,输出的run
变量的值变成了子类Dog
中的run
方法的输出,而不是Animal
类中的run
方法的输出,这是因为:当子类和父类同时存在相同的方法时,在调用子类的时候,子类的方法会覆盖掉父类的方法。
例如:
Dog
是Animal
的子类,并且Dog
和Animal
类中都有run
的方法,在这种情况下调用Dog
的实例dog.run()
,输出的是Dog
类中run
方法的输出,而不是Animal
的
看过上面的案例之后,我们又获得了继承的另一个好处,多态
当我们定义一个类时,我们实际上就定义了一种新的数据类型
,并且这个数据类型和Python的基础数据类型,列表、字符串、字典等没有什么太大的区别,例如:
- 先定义三个变量为不同的实例
>>> a = list()
>>> b = Animal()
>>> c = Dog()
- 使用'isinstance()'进行判断
>>> isinstance(a,list)
True
>>> isinstance(b,Animal)
True
>>> isinstance(c,Dog)
True
>>> isinstance(c,Animal)
True
>>> isinstance(b,Dog)
False
可以看到'a'、'b'、'c'分别对应着'list'、'Animal'、'Dog'三种类型,并且'c'不仅对应着'Dog',而且还对应的'Animal',这是因为'Dog'是从'Animal'继承下来的,即'Dog'本身就是'Animal'的一种,所以'c'才可以对应两种类型
#注意:
在继承关系中,如果一个实例的数据类型是某个子类,那么这个实例的数据类型也可以被看作是父类,但是,父类不能看作是子类,可以看到最后一个'isinstance(b,Dog)'的输出是'False'
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal
类型的变量:
>>> def run_twice(animal):
... animal.run()
... animal.run()
...
>>> run_twice(Animal())
Animal is running!!!
Animal is running!!!
>>> run_twice(Dog())
Dog is running!!!
Dog is running!!!
- 只看上面的感觉没有什么,但是现在来创建一个新的'Animal'的子类,去调用'run_twice'函数
>>> class Tortoise(Animal):
... def run(self):
... return print('Tortoise is running slowly!!!')
...
>>> run_twice(Tortoise())
Tortoise is running slowly!!!
Tortoise is running slowly!!!
可以发现新创建的子类'Tortoise'可以在调用'run_twice'函数时作为参数直接传入,并且可以正常运行,输出的数据都不一样,这就是因为'多态'的特性
看过上面的案例后,其实可以看出多态的作用就是:让具有不同功能的函数可以使用相同的函数名称,从而可以使用一个函数名调用不同功能的函数
多态的特点:
- 只需要关心对象的方法名是否相同,不需要关系对象所属的类型
- 对象所属的类之间,继承关系可有可无,因为就算是子类,但是只要有相应的方法,最终还是会按照子类的方法执行,父类的方法不用考虑
- 多态还可以增加代码的外部调用灵活度,让代码更加通用,兼容性更强
- 多态是调用方法的技巧,不会影响类的内部设计
对于多态的个人理解:
其实就是在把类作为参数传入函数时,无需考虑是什么类
(不管是子类还是父类)
,只要说传入类的方法和函数内调用的方法名称是相同的就行,例如:这里定义一个run_test函数 >>> def run_test(aaaa): ... aaaa.run() ... 根据上面的函数内容,我们在传入类时,其实只要保证类中有'run'的方法就行,就跟上面的案例一样,在传入'Dog'、'Animal'后,输出的内容都是他们自己'run'方法的操作步骤 >>> class Animal(object): ... def run(self): ... return print('Animal is running!!') ... >>> class Dog(Animal): ... def run(self): ... return print('Dog is running !!!') ... >>> run_test(Dog()) Dog is running !!! >>> run_test(Animal()) Animal is running!! 但是比如传入的'Dog'类中并没有新定义'run'方法,那么他会从'Animal'类中继承'run'方法,那么输出的就是'Animal'的'run'方法了, >>> class Dog(Animal): ... pass ... >>> run_test(Dog()) Animal is running!! 如果父类'Animal'也没有'run'方法的话,就会报错 >>> class Animal(object): ... pass ... >>> run_test(Dog()) #在重新定义Animal后,先传入Dog发现输出还是原来的,需要重新定义Dog Animal is running!! >>> class Dog(Animal): ... pass ... >>> run_test(Dog()) Traceback (most recent call last): File "
" , line 1, in <module> File "" , line 2, in run_test AttributeError: 'Dog' object has no attribute 'run'
扩展:
- 根据上面调用函数传入类来说,静态语言和动态语言是不一样的
- 对于静态语音例如JAVA来说,如果需要传入的类是
Animal
,那么传入的参数必须是Animal
类或者它的子类,否则就算传入的类有run
方法,也是无法调用的- 但是对于动态语言来说,只要保证传入的类中有
run
方法即可,这就是动态语言的鸭子类型
使用type()
函数可以判断对象类型,下面直接来看案例:
>>> type(123)
<class 'int'>
>>> type('123')
<class 'str'>
>>> type(None)
<class 'NoneType'>
- 当一个变量指向函数或者类,也可以使用type判断
>>> class Animal(object):
... pass
...
>>> type(abs) #判断abs函数
<class 'builtin_function_or_method'>
>>> a = Animal()
>>> type(a) #判断a实例
<class '__main__.Animal'>
- 可以看到type返回的信息中,都是以'class'开头的,其实可以看出返回的都是'对象'对应的'类',可以使用'if'语句进行判断
>>> type(123) == type(345)
True
>>> type(123) == int
True
>>> type('123') == str
True
>>> type('123') == type(123)
False
- 从上面可以看出,判断基本的数据类型可以使用'int','str'等,但是如果想要判断一个对象是否是函数呢?可以使用'Types'模块中定义的常量进行判断
>>> 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
对于类的继承关系来说,使用Type()
就很不方便,我们想要判断类的类型,可以使用isinstance()
函数
- 现在来创建几个类
>>> class Animal(object):
... def run(self):
... return print('Animal is running!!!')
...
>>> class Dog(Animal):
... def run(self):
... return print('Dog is running!!!')
...
现在的继承关系为:object——>Animal——>Dog
-现在使用'isinstance()'去判断
>>> a = Animal()
>>> b = Dog()
>>> isinstance(a,Animal)
True
>>> isinstance(b,Dog)
True
>>> isinstance(b,Animal)
True
>>> isinstance(b,object)
True
>>> isinstance(a,Dog)
False
可以看到Dog因为是子类,所以既是'Animal'、'Dog'也是'object',和上面的一样,子类可以当作父类,而父类不能当作子类
一般来说可以使用type()
判断的类型,也可以使用isinstance()
进行判断
>>> isinstance('a',str)
True
>>> isinstance(123,int)
True
>>> isinstance(b'a',bytes)
True
还可以判断一个变量是否是某种类型的一种,其实就是在后面多加几中数据类型,判断是否在多种数据类型之中,例如:
>>> isinstance([1,2,3],(list,tuple))
True
>>> isinstance((1,2,3),(list,tuple))
True
>>> isinstance(1,(str,int))
True
>>> isinstance('1',(str,int))
True
推荐直接使用isinstance()
进行判断,可以将指定类型以及子类一次性进行判断
如果想要获取指定对象的所有属性和方法
,可以使用dir()
函数,dir()
函数会返回一个包含字符串的list,例如:
>>> dir("abc")
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>> dir(123)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
上面说过了,类似于__xx__
这种的属性和方法在Python中都是有特殊用途的,例如:
- 在Python调用'len()'函数,实际上,在'len()'函数的内部,会自动去调用'__len__()'方法,所以下面的作用是相同的
>>> len("aaa")
3
>>> "aaa".__len__()
3
在创建类时,也可以加__len__()
方法:
>>> class Test(object):
... def __len__(self):
... return 100
...
>>> aaa = Test()
>>> len(aaa)
100
>>> class Test(object):
... def __init__(self,name):
... self.name = name
... def __len__(self):
... return len(self.name)
...
>>> aaa = Test('aaaaaa')
>>> aaa.__len__()
6
剩下的都是普通属性和方法,例如:
- lower()返回小写的字符串
>>> 'AAAAA'.lower()
'aaaaa'
配合getattr()
、setattr()
、hasattr()
可以直接操作一个对象的状态:
#注释:
getattr(实例名称,"属性"):获取指定'实例'的'属性'
setattr(实例名称,"属性","设置属性的值"):设置指定'实例'的'属性'
hasattr(实例名称,"属性"):判断'实例'是否有指定的'属性'
>>> class Test(object):
... def __init__(self,name):
... self.name = name
... def cj(self):
... return self.name * self.name
...
>>> aaa = Test(9)
>>> hasattr(aaa,'name')
True
>>> aaa.name
9
>>> hasattr(aaa,'sorce')
False
>>> setattr(aaa,'sorce',20)
>>> hasattr(aaa,'sorce')
True
>>> aaa.sorce
20
>>> getattr(aaa,'sorce')
20
>>> getattr(aaa,'acx') #没有则会报错
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: 'Test' object has no attribute 'acx'
>>> getattr(aaa,'acx',123) #如果acx属性不存在,则返回默认值123
123
除了判断实例的属性,还可以判断实例的方法
,这个方法是对象里的方法
>>> hasattr(aaa,'cj')
True
>>> getattr(aaa,'cj') #可以看到判断是一个函数
<function Test.cj at 0x000001B6A277ECB0>
>>> bbb = getattr(aaa,'cj')
>>> bbb
<bound method Test.cj of <__main__.Test object at 0x000001B6A2756D40>>
>>> bbb()
81
小结:
- 通过上面所说的一系列内置函数,我们可以对任意一个Python对象进行解析,需要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息,例如:
可以写: sum = obj.x + obj.y 就不要这样写: sum = getattr(obj, 'x') + getattr(obj, 'y')
- 一般来说可以利用这些内置函数做if判断,判断一个对象丽是否存在指定的方法
def readImage(fp): if hasattr(fp, 'read'): return readData(fp) return None
由于Python是动态语言,所以根据类创建的实例可以任意绑定属性
,而给实例绑定属性的方法就是通过创建实例时设置的变量,即实例变量(实例属性)
,或者通过self变量
:
>>> class Student(object):
... def __init__(self,name):
... self.name = name
...
>>> test = Student('zhangsan') #这种在创建实例时设置或者使用test.score创建的属性都叫实例属性,和类属性不互通
>>> test.score = 98
>>> test.score
98
>>> test.name
'zhangsan'
如果Student
类本身就需要绑定一个属性的话,可以直接在class中定义属性,而这种属性叫做类属性
,归Student
类所有:
>>> class Student(object):
... name = 'zhangsan'
...
>>> test = Student()
>>> test.name #可以看到,test实例是没有设置name属性的,但是输出的数据是类中的name属性的值,这是因为当实例没有该属性时,会继续查找类中的相应属性的值
'zhangsan'
>>> Student.name
'zhangsan'
>>> test.name = 'lisi' #给test实例的name属性赋值
>>> test.name
'lisi'
>>> Student.name
'zhangsan'
>>> del test.name #删除test实例的name属性的值
>>> test.name #发现又变成了类的name属性
'zhangsan'
从上面可以看出,当实例没有指定的属性时,python会寻找类的属性,所以说,在编写代码时,不要对实例属性和类属性设置相同的名称
,因为相同名称的情况下,实例属性会覆盖掉类属性
,当删除实例属性后,再次使用相同的名称进行访问,将会重新访问到类属性