Python3.5笔记——第8章 面向对象编程
Python3.5笔记
第8章 面向对象编程
面向对象术语介绍
- 类:用来描述具有相同属性和方法的对象的集合。类定义中集合了每个对象共有的属性和方法。对象是类的示例。
- 类变量(属性):类属性在整个实例化的对象中是公用的。类变量定义在类中,且在方法之外。类常亮通常不作为适量变量使用。类变量也称作属性。
- 数据成员:类变量或实例变量用于处理类及其实例变量的相关数据。
- 方法重写:如果从父类继承的方法,不能满足子类的需求,就可以对其重写,这个过程叫做方法的覆盖,也称为方法的重写。
- 实例变量:定义在方法中的变量,只作用于当前实例的类。
- 多态:对不同类的对象使用同样的操作。
- 封装:对外部对象隐藏对象的工作细节。
- 继承:即一个派生类继承基类的字段和方法。继承允许把一个派生类的对象作为一个基类对象对待,以普通类为基础建立专门的类对象。
- 实例化:创建一个类的实例,类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
类的定义
示例:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class MyClass(object):
i = '小明'
def f(self):
return 'hello world %s' % self.i
类定义的写法:
- 使用class关键字,class关键字后面紧跟类名,类名通常首字母大写
- 类名后紧跟的是(object),表明该类是从哪个类中继承而来的,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
- 类通常包含属性和方法。在类中定义方法时,第一个参数必须是self。除第一个参数外,类的方法和普通函数没有什么区别。
类的使用
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class MyClass(object):
i = '小明'
def f(self):
return 'hello world %s' % self.i
my_class = MyClass()
print('类中的属性是:',my_class.i)
print('类中方法的返回是:',my_class.f())
输出:
类中的属性是: 小明
类中方法的返回是: hello world 小明
类的使用比函数调用多了几个操作:
- my_class = MyClass() 这步叫做类的实例化,即创建一个类的实例。由此得到的my_class变量称为类的具体对象。
- 调用类中定义的方法,除了self不用传递外,其他参数正常传入。
- 类属性引用的语法为:obj.name,obj为类对象,name代表属性。
类的构造方法
在Python中,_init()方法是一个特殊方法,在对象实例化时会被调用。这个方法的写法是:先输入两个下划线,后面接着输入init,再接着两个下划线。这个方法也叫构造方法。在定义类时,如果不显示的定义_init()方法,则程序默认会调用一个无参的构造方法。示例如下:
class MyClass2(object):
i = 123
def __init__(self,name1,age1):
self.name = name1
self.age = age1
def f(self):
return 'hello %s,you are %s years old!' % (self.name,self.age)
my_class2 = MyClass2('小明','23')
print('类中的属性是:',my_class2.name)
print('类中的属性是:',my_class2.age)
print('类中方法的返回是:',my_class2.f())
输出:
类中的属性是: 小明
类中的属性是: 23
类中方法的返回是: hello 小明,you are 23 years old!
一个类中可以定义多个构造方法,但实例化类时只实例化最后的构造方法,即后面的构造方法会覆盖前面的构造方法,并且需要根据最后一个构造方法的形参进行实例化。建议一个类中,只定义一个构造方法。
类的访问权限
直接访问公有属性和方法
在类的内部有属性和方法,外部代码可以直接调用属性和方法。示例如下:
#! /user/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
print('hello %s,you are %s years old!' % (self.name,self.age))
stu = Student('小明','23')
stu.info()
stu.name = '小张'
stu.age = '24'
stu.info()
输出:
hello 小明,you are 23 years old!
hello 小张,you are 24 years old!
如何定义和访问私有属性
要让类的内部属性不被外部访问,可以在属性名称前加两个下划线__。在Python中,实例的变量名如果以两个下划线开头,就会变成私有变量,只有内部可以访问,外部不能访问。此时,如果直接访问就会出错。示例如下:
#! /user/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
def info(self):
print('hello %s,you are %s years old!' % (self.__name, self.__age))
stu = Student('小明','23')
stu.info()
stu.__name = '小张'
stu.__age = '24
stu.info()
输出:
Traceback (most recent call last):
File "D:/pyspace/hellopython/Chapter8.py", line 60, in <module>
stu.info()
File "D:/pyspace/hellopython/Chapter8.py", line 57, in info
print('hello %s,you are %s years old!' % (self.name,self.age))
AttributeError: 'Student' object has no attribute 'name'
这时,可以通过为类增加get_attrs()方法,获取类中的私有变量。可以为类添加set_attrs()方法,修改类中的私有变量。示例如下:
class Student(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
def info(self):
print('hello %s,you are %s years old!' % (self.__name,self.__age))
def get_name(self):
return self.__name
def set_name(self,name):
self.__name = name
def get_age(self):
return self.__age
def set_age(self,age):
self.__age = age
stu = Student('小明','23')
stu.info()
stu.set_name('小张')
print('修改后的姓名是:',stu.get_name())
stu.set_age('24')
print('修改后的年龄是',stu.get_age())
输出:
hello 小明,you are 23 years old!
修改后的姓名是: 小张
修改后的年龄是 24
如何定义和访问私有方法
通过在方法名前加两个下划线,可以把方法定义为私有方法。私有方法不能通过外部代码直接访问,只能在类中通过公共方法调用。实例如下:
#! /user/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self,name,age):
self.name = name
self.age = age
def __info(self):
print('hello %s,you are %s years old!' % (self.name,self.age))
def foo(self):
self.__info()
stu = Student('小明','23')
stu.foo()
输出:
hello 小明,you are 23 years old!
继承
当我们定义一个class时,可以从某个现有的class继承,定义的新class称为子类(SubClass),而被继承的class称为基类、父类或者超类(BaseClass、Super Class)。继承的格式如下:
class DerivedClassName(BaseClassName):
statemnt-1
...
statement-n
继承语法class子类名(基类名)时,基类名写在括号内,在元组中指明。特点如下:
- 在继承中,基类的构造方法不会被自动调用,需要在子类的构造方法中专门调用。
- 在调用基类的方法时,需要加上基类的类名前缀,并带上self参数变量。区别于在类中调用普通方法时,不需要带self参数。
- 在Python中,首先查找对应类型的方法,如果在子类中找不到对应的方法,才到基类中逐个查找。
class Animal(object):
def run(self):
print('animal is running....')
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.run()
cat = Cat()
cat.run()
输出:
animal is running....
animal is running....
子类不能继承父类的私有方法,也不能调用父类的私有方法。
多态
当子类和父类有相同的方法时,子类的方法会覆盖父类的方法,在方法调用时,总是会首先调用子类的方法,这种情况就是多态。示例如下:
#! /user/bin/python3
# -*- coding:UTF-8 -*-
class Animal(object):
def run(self):
print('animal is running....')
class Dog(Animal):
def run(self):
print('dog is running....')
class Cat(Animal):
def run(self):
print('cat is running...')
dog = Dog()
dog.run()
cat = Cat()
cat.run()
print('dog是否是animal类型',isinstance(dog,Animal))
print('dog是否是dog类型',isinstance(dog,Animal))
输出:
dog is running....
cat is running...
dog是否是animal类型 True
dog是否是dog类型 True
例如上面的示例中,dog和cat分别继承了animal,并且分别定义了自己的run方法,最后调用的是各自的run方法。
使用多态的好处是:当我们需要传入Dog,Cat等子类对象时,只需要接收父类Animal对象就可以了,因为Dog,Cat等都是Animal类型,按照Animal类型传入参数即可。由于Animal类型都有run()方法,因此传入的类型只要是Animal类或者是Animal的子类,都会自动调用实例类型的方法。
多重继承
上一节讲述的是单继承,Python还支持多重继承。多重继承的类定义如下:
class DerivedClassName(Base1,Base2,Base3):
statment-1
...
statement-n
可以看到,多重继承就是有多个基类(父类或者超类)。
需要注意圆括号中父类的顺序,若父类中有相同的方法名,在子类使用时未指定,Python会从左到右搜索。若方法在子类中未找到,则从左到右查找父类中是否包含此方法。
获取对象信息
使用type()函数
判断基本类型可以用type()函数判断。如:
print('-----------type()函数-----------')
print(type('abc'))
print(type(123))
print(type(None))
print(type(abs))
print(type('abc')==str)
print(type(123)==int)
print('-----------判断一个对象是否是函数-----------')
import types
def func():
pass
print(type(func)==types.FunctionType)
print(type(abs)==types.BuiltinFunctionType)
print(type(lambda x,y,z:x+y+z)==types.LambdaType)
print(type(x for x in range(1,10))==types.GeneratorType)
输出:
-----------type()函数-----------
<class 'str'>
<class 'int'>
<class 'NoneType'>
<class 'builtin_function_or_method'>
True
True
-----------判断一个对象是否是函数-----------
True
True
True
True
使用isinstance()函数
使用isinstance()函数可以告诉我们一个对象是否是某种类型。
print('dog是否是animal类型',isinstance(dog,Animal))
print('dog是否是dog类型',isinstance(dog,Animal))
输出
dog是否是animal类型 True
dog是否是dog类型 True
使用dir()函数
如果要获得一个对象的所有属性和方法,就可以使用dir()函数。dir()函数返回一个字符串的list。如:
print(dir('abc'))
输出:
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__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', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
类的专有方法
_str_
相当于java中的to_string()方法。如:
class Student(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
def __str__(self):
return '学生名称是:%s,学生的年龄是:%s' % (self.__name,self.__age)
print(Student('小明','23'))
stu_abc = Student('小宇','24')
print(stu_abc)
输出:
学生名称是:小明,学生的年龄是:23
学生名称是:小宇,学生的年龄是:24
注意:_str_方法必须返回一个字符串
_iter_
如果要将一个类用于for...in循环,类似list或者tuple一样,就必须实现一个_iter_()方法。该方法返回一个迭代对象,Python的for循环会不断调用迭代对象的__next()方法,获得循环的下一个值,直到遇到StopIteration错误时退出循环。如:
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a、b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration();
return self.a # 返回下一个值
for n in Fib():
print(n)
输出:
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
_getitem()_
要像list一样按照下标取出元素,需要实现_getitem()_方法,示例如下:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
print(Fib()[3])
输出:
3
_getattr_
使用_getattr_方法动态返回一个属性。如:
class Student1(object):
def __getattr__(self, attr):
if attr == 'score':
return 95
stu1 = Student1()
print(stu1.score)
输出:
95
_call_
通过给一个类定义_call方法,可以直接对实例进行调用并得到结果。_call还可以定义参数。对实例进行直接调用就像对一个函数调用一样,完全可以把对象看成函数,把函数看成对象。因为这两者本来就没有根本区别。如果把对象看成函数,函数本身就可以在运行期间动态创建出来,因为类的实例都是运行期间创建出来的。这样一来,就模糊了对象和函数的界限。实例如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self,name):
self.name = name
def __call__(self, *args, **kwargs):
print('姓名是:%s' % self.name)
stu2 = Student('小明')
stu2()
输出:
姓名是:小明
可以使用Callable()函数,判断一个对象是否可以被调用。实例如下:
print(callable(Student('小明')))
print(callable(Student1()))
print(callable(max))
print(callable([1,2,3]))
输出:
True
False
True
False
牛刀小试——出行建议
小智今天想出去,但不清楚今天的天气是否适合出行,需要一个给他提供建议的程序,程序建议要求输入daytime和night,根据可见度和温度给出出行建议和使用的交通工具,需要考虑需求变更的可能。
需求分析:使用本章所学的封装、继承、多态比较容易实现,由父类封装查看可见度和温度的方法,子类继承父类。如果有需要,子类可以覆盖父类的方法,做自己的实现。子类也可以自定义方法。
class WeatherSeach(object):
def __init__(self,input_daytime):
self.input_daytime = input_daytime
def search_visible(self):
visible = 0
if self.input_daytime == 'daytime':
visible = 6
elif self.input_daytime == 'night':
visible = 2
return visible
def search_temperature(self):
temperature = 0
if self.input_daytime == 'night':
temperature = 16
elif self.input_daytime == 'daytiem':
temperature = 26
return temperature
class OutAdvice(WeatherSeach):
def __init__(self,input_daytime):
WeatherSeach.__init__(self,input_daytime)
def search_temperature(self):
we_use = 'none'
if self.input_daytime == 'daytime':
we_use = '骑自行车'
elif self.input_daytime == 'night':
we_use = '开车'
return we_use
def take_advice(self):
we_use = self.search_temperature()
visible = self.search_visible()
if visible >= 6:
print('今天天气不错,可以%s出行' % we_use)
elif 0 < visible <= 2:
print('今天天气不好,只能%s出行' % we_use)
else:
print('输入的内容有误,无法给出相应的建议!')
oa = OutAdvice('night')
oa.take_advice()
输出
今天天气不好,只能开车出行
问题解答
双下划线开头的实例变量一定不能从外部访问吗?
答:不是,不能直接访问的原因是Python解释器对外把__score变量变成了_Student__score,所以,仍然可以通过Student__score访问变量__score。如:
class Student(object):
def __init__(self,name,score):
self.__name = name
self.__socre = score
def info(self):
print('学生的姓名是:%s,成绩是:%s' % (self.__name,self.__socre))
def get_score(self):
return self.__socre
stu3 = Student('小宇','94')
stu3.info()
#print('分数是:',stu3._Student__score) #报错
print('分数是:',stu3.get_score())
输出:
学生的姓名是:小宇,成绩是:94
分数是: 94
方法和函数的区别
答:在Python中,函数并不依附于类,在不在类中定义。而方法依附于类,定义在类中,本质上还是一个函数。为便于区分,我们把类中的函数叫做方法,不依赖于类的函数仍然称为函数。
为什么要使用类
答:在Python中,借助继承、封装、多态三大特性,使用类可以更好的对一类实物进行管理,可以将具有相同功能和行为的事物封装为一个类,其他具有相同特性的事物直接继承类,即可获得父类封装好的方法。同时,子类可以覆盖父类的方法,以满足特定的功能需求,子类也可以扩展自己的功能。使用类可以更好的实现代码的复用和扩展。