![cover](https://img-blog.csdnimg.cn/img_convert/fad536e972e14ce4b37803185dc3b00c.png)
python类--定义及使用(类的属性及方法)
python类对象
一、要点回顾(python对象)
1.1、python对象基础知识
Python是一门面向对象(object)的语言,一切皆对象!
-
程序运行当中,所有的数据都是存储到内存当中然后再运行的!
-
对象就是内存中专门用来存储指定数据的一块区域
-
对象实际上就是一个容器,专门用来存储各种数据(比如:数字、布尔值、代码)
对象由三部分组成:
- 对象的标识(id)
- id用来标识对象的唯一性,每一个对象都有唯一的id(相当于人的身份证号)
- id是由解析器生成的,在CPython中,id就是对象的内存地址,对象一旦创建,则它的id永远不能再改变
- 可以通过id()函数来查看对象的id
- 对象的类型(type)
- 类型用来标识当前对象所属的类型,比如:int str float bool…
- 类型决定了对象有哪些功能,python是一门强类型的语言,对象一旦创建类型便不能修改
- 可以通过type()函数来查看对象的类型
- 对象的值(value)
- 值就是对象中存储的具体的数据
- 有些对象的值可以改变(可变对象),有些对象的值不可以改变(不可变对象)
对象与变量
- 对象并没有直接存储到变量中,在python中变量更像是给对象起了一个别名
- 变量中存储的不是对象的值,而是对象的id(内存地址)
- 当我们使用变量时,实际上就是在通过对象id在查找对象
- 变量中保存的对象,只有在为变量重新赋值时才会改变
- 变量和变量之间是相互独立的,修改一个变量不会影响另一个变量
1.2、面向对象编程
- python是一门面向对象的编程语言,简单理解就是语言中的所有操作都是通过对象来进行的
- 面向过程的编程的语言
- 面向过程的编程思想将一个功能分解为一个一个小的步骤,通过完成一个一个的小的步骤来完成一个程序
- 例子:
- 孩子上学
- 1.妈妈起床
2.妈妈洗漱
3.妈妈做早饭
4.妈妈叫孩子起床
5.孩子上厕所
6.孩子要洗漱
7.孩子吃饭
8.孩子背着书包上学校
- 1.妈妈起床
- 孩子上学
- 优点:符合思维惯性,编写起来相对比较简单
- 缺点:复用性比较低,并且难于维护(往往只适用于一个功能,如果要实现别的功能,即使功能相差极小,也要重新编写代码)
- 面向对象的编程语言
- 面向对象的编程语言,关注的是对象,而不关注过程
- 例子:
- 孩子上学
- 1.妈妈起床叫孩子上学
- 孩子上学
- 面向对象的编程思想,将所有的功能统一保存到对应的对象中
- 妈妈功能保存到妈妈的对象中,孩子的功能保存到孩子对象中,要使用某个功能,直接找到对应的对象即可
- 优点:容易阅读,并且比较易于维护,容易复用
- 缺点:不太符合常规的思维,编写起来稍微麻烦
二、python类
2.1、类简介及定义方式
2.1.1、类中的属性及方法
-
常用的对象都是Python内置的对象
-
# int() float() bool() str() list() dict() .... 这些都是类 a = int(10) # 创建一个int类的实例 等价于 a = 10 print(type(a)) # <class 'int'>
-
但是内置对象并不能满足所有的需求,经常需要自定义一些对象**(根据类创建对象)**
- 类就是对象的图纸!对象是类的实例(instance)
- 类也是一个对象!类是一个用来创建对象的对象!
类的定义方式,使用class关键字
-
在定义类时,可以定义变量(属性)和函数(方法)
-
类属性:在类中定义的变量,是公共属性,
- 访问:类属性可以通过类或类的实例访问到(对象.属性名)
- 修改:类属性只能通过类对象来修改,无法通过实例对象修改
-
实例属性:通过实例对象添加的属性属于实例属性(在init()方法中定义)
- 实例属性只能通过实例对象来访问和修改(对象.属性名),类对象无法访问修改
-
方法:在类中定义的函数(方法),可以通过该类的所有实例来访问(对象.方法名())
-
实例方法:在类中定义,以self为第一个参数的方法都是实例方法
- 实例方法在调用时,python会将调用对象作为self传入
- 实例方法可以通过实例和类去调用
- 当通过实例调用时,会自动将当前调用对象作为self传入
- 当通过类调用时,不会自动传递self,此时我们必须手动传递self
-
类方法:在类内部使用 @classmethod 修饰的方法(装饰器,详见3.1.2)
- 类方法的第一个参数是cls,也会被自动传递,cls就是当前的类对象
- 实例方法的第一个参数是self,而类方法的第一个参数是cls
- 类方法可以通过类去调用,也可以通过实例调用,没有区别
- 类方法的第一个参数是cls,也会被自动传递,cls就是当前的类对象
-
静态方法:在类中使用 @staticmethod 修饰的方法
- 静态方法不需要指定任何的默认参数,可以通过类和实例去调用
- 静态方法,基本上是一个和当前类无关的方法(常作为工具方法),它只是一个保存到当前类中的函数
-
方法示例见2.3.2
-
-
类中方法的self形参说明:
- 方法调用时,第一个参数由解析器自动传递,所以定义方法时,至少要定义一个形参!
-
在方法中不能直接访问类中的属性(包括公共属性)
2.1.2、类的定义
# 我们自定义的类都需要使用大写字母开头,使用大驼峰命名法(帕斯卡命名法)来对类命名
class 类名(父类) : # 默认继承object类,可不写
# 公共属性
public_attribute = ''
# 对象的初始化方法(非必要,通常会有该方法)
def __init__(self,...):
pass
# 其他的方法
def method_1(self,...):
pass
def method_2(self,...):
pass
"""
练习:尝试自定义一个表示狗的类(Dog)
属性:
name
age
方法:
jiao()
run()
"""
class Dog(object) :
# 主人姓名
owner = 'qy'
# 对象的初始化方法
def __init__(self, name, age):
self.name = name
self.age = age
# 其他的方法
def jiao(self):
print("汪汪汪")
def run(self):
print("跑")
# 在方法中访问类的属性
def say_hello(self):
print("你好,%s" % self.owner)
2.2、使用类创建对象
2.2.1、创建对象
使用类创建对象与调用函数的方式类似,对象名 = 类名(“用于初始化的参数”)
- 创建一个变量(对象名)
- 在内存中创建一个新对象(类的实例),如果有init(self)方法则执行
- 将对象的id赋值给变量
dog1 = Dog("xiaobai", 3)
print(dog1.owner) # qy
print(dog1.name) # xiaobai
dog1.name = "xiaohei"
print(dog1.name) # xiaohei
dog1.jiao() # 汪汪汪
dog1.run() # 跑
dog1.say_hello() # 你好,qy
dog2 = Dog("dahuang", 2)
dog2.jiao() # 汪汪汪
# 方法每次被调用时,解析器都会自动传递第一个实参(调用方法的对象本身)给self,
# 如果是dog1调的,则第一个参数就是dog1对象
# 如果是dog2调的,则第一个参数就是dog2对象
使用isinstance()检查一个对象是否是一个类的实例
flag = isinstance(dog1, dog) # true
flag = isinstance(dog1, int) # false
2.2.2、设置及访问对象属性
没有init(self)函数时,可以直接设置并修改对象的属性(不建议)
# 也就是对象中实际上什么都没有,就相当于是一个空的盒子
# 可以向对象中添加变量,对象中的变量称为属性(对象.属性名 = 属性值)
class MyClass():
pass
mc1 = MyClass()
result = isinstance(mc1, MyClass) # true
print(id(mc1), type(mc1)) # 2841238613072 <class '__main__.MyClass'>
print(id(MyClass), type(MyClass)) # 2841239528544 <class 'type'>
mc1.name = '孙悟空'
print(mc1.name) # 孙悟空
mc1.name = '猪八戒'
print(mc1.name) # 猪八戒
# 手动给类的对象添加、修改属性容易出现错误,且不够清晰,不容易记忆
# 如果明确类的对象必须有name,则可以将该属性设置到初始化函数中,即init(self, name),在创建对象时,必须设置name属性,如果不设置对象将无法创建
# class MyClass():
# def __init__(self, name):
# # 通过self向新建的对象中初始化属性
# self.name = name
# mc2 = MyClass("唐僧")
类对象和实例对象中都可以保存属性(方法)
-
如果这个属性(方法)是所有的实例共享的,则应该将其保存到类对象中
-
如果这个属性(方法)是某个实例独有,则应该保存到实例对象中
-
一般情况下,属性保存到实例对象中,而方法需要保存到类对象中
属性和方法查找的流程
- 当我们调用一个对象的属性时,解析器会先在当前对象中寻找是否含有该属性
- 如果有,则直接返回当前的对象的属性值
- 如果没有,则去当前对象的类对象中去寻找
- 如果有则返回类对象的属性值
- 如果类对象中依然没有,则报错!
2.3、类的特殊方法
2.3.1、特殊方法
在类中可以定义一些特殊方法(魔术方法)
- 特殊方法都是以__开头,__结尾的方法
- 特殊方法一般不需要自己调用(不建议手动调用),特殊方法将会在特殊的时刻自动调用
- _init_()是常用的特殊方法
- 创建对象时执行,创建以后结束执行(可以用来对新创建的对象初始化属性)
- 创建对象的流程:dog1 = Dog(“xiaobai”, 3)
- 创建一个变量dog1
- 在内存中创建一个新对象(Dog实例)
- _init_(self)方法执行
- 将对象的id赋值给dog1
其他特殊方法
# 定义一个Person类,默认继承Object类
class Person():
"""人类"""
def __init__(self, name , age):
self.name = name
self.age = age
# object.__str__(self)
# 会在尝试将对象转换为字符串的时候调用
# 它的作用可以用来指定对象转换为字符串的结果(print函数)
# 相当于方法重写
def __str__(self):
return 'Person [name=%s , age=%d]'%(self.name,self.age)
# object.__bool__(self)
# 可以通过bool来指定对象转换为布尔值的情况
def __bool__(self):
return self.age > 17
# object.__gt__(self, other) 大于
# 需要两个参数,一个self表示当前对象,other表示和当前对象比较的对象
def __gt__(self , other):
return self.age > other.age
# 创建两个Person类的实例
p1 = Person('孙悟空',18)
p2 = Person('猪八戒',28)
# 打印p1
# 当我们打印一个对象时,实际上打印的是对象的中特殊方法 __str__()的返回值
print(p1) # Person [name=孙悟空 , age=18]
print(bool(p1)) # True
if p1:
print(p1.name,'已经成年了')
else:
print(p1.name,'还未成年了')
print(p1 > p2) # False
print(p1 < p2) # True
2.3.2、类方法与静态方法
# 定义一个类
class A(object):
"""
类属性
实例属性
类方法
实例方法
静态方法
"""
# 类属性
count = 0
def __init__(self):
# 实例属性
self.name = '孙悟空'
# 实例方法
# 在类中定义,以self为第一个参数的方法都是实例方法
def test(self):
print('这是test方法~~~' , self)
# 类方法
# 在类内部使用 @classmethod 来修饰的方法属于类方法
@classmethod
def test_2(cls):
print('这是test_2方法,他是一个类方法~~~ ',cls)
print(cls.count)
# 静态方法
# 在类中使用 @staticmethod 来修饰的方法属于静态方法
@staticmethod
def test_3():
print('test_3执行了~~~')
a = A()
a.test() # 这是test方法~~~ <__main__.A object at 0x000002870FBCC670>
# 通过类调用实例方法,必须手动传递self
A.test(a) # 这是test方法~~~ <__main__.A object at 0x000002870FBCC670>
A.test_2() # 等价于 a.test_2()
# 这是test_2方法,他是一个类方法~~~ <class '__main__.A'>
# 0
A.test_3() # 等价于a.test_3()
# test_3执行了~~~
三、python类的特性
面向对象的三大特征:
- 封装:确保对象中的数据安全
- 继承:保证了对象的可扩展性
- 多态:保证了程序的灵活性
3.1、封装
面向对象编程,即定义类(创建对象),以类的变量(属性)和函数(方法)为核心
封装是面向对象的三大特性之一
- 隐藏对象中一些不希望被外部所访问到的属性或方法
3.1.1、如何隐藏对象的属性?
可选方式一:将属性名设置为外部未知,并提供getter和setter方法使外部可以访问、设置属性值
class Dog:
def __init__(self , name , age):
self.hidden_name = name
def get_name(self):
return self.hidden_name
def set_name(self , name):
self.hidden_name = name
d = Dog('旺财')
# 调用setter来修改name属性
d.set_name('小黑')
print(d.get_name()) # 小黑
使用封装,增加了类的复杂度,但是也确保了数据的安全
- 隐藏了属性名,使调用者无法随意的修改对象中的属性
- 增加了getter和setter方法,很好的控制的属性是否是只读的
- 如果希望属性是只读的,则可以直接去掉setter方法
- 如果希望属性不能被外部访问,则可以直接去掉getter方法
- 可以在读取属性和修改属性的同时做一些其他的处理(不建议)
- 例如,使用setter方法设置属性,增加数据的验证逻辑,确保数据的值是正确的
可选方式二:使用双下划线开头,__属性名
- 双下划线开头的属性,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问
- 其实隐藏属性只不过是python自动为属性改了一个名字
- 实际上是将名字修改为了,_类名属性名
class Dog:
def __init__(self , name):
self.__name = name
dog1 = Dog("xiaobai")
# print(dog1.__name) # 'Dog' object has no attribute '__name'
print(dog1._Dog__name) # xiaobai
- 使用__开头的属性,实际上依然可以在外部访问,所以这种方式一般不采用
可选方式三:使用单下划线开头,_属性名
- 一般我们会将一些私有属性(不希望被外部访问的属性)以_开头
- 一般情况下,使用_开头的属性都是私有属性,没有特殊需要不要修改私有属性
- 私有属性可以被类对象访问、设置(防君子不防小人)
class Person:
def __init__(self,name):
self._name = name
def get_name(self):
return self._name
def set_name(self , name):
self._name = name
p = Person('孙悟空')
print(p._name) # 孙悟空
p._name = '猪八戒'
print(p._name) # 猪八戒
3.1.2、使用装饰器,方便访问、设置属性
装饰器就是函数闭包的一种应用
- 是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数
- 使用python装饰器能在不用更改原函数的代码前提下给函数增加新的功能
函数闭包相关内容:http://t.csdn.cn/w1lHd
常用装饰器
- property装饰器(@property),能够将get方法转换为对象的属性;
- 使用property装饰的方法,必须和属性名是一样的
- setter方法的装饰器(@属性名.setter)
class Person:
def __init__(self,name,age):
self._name = name
self._age = age
# property装饰器,用来将一个get方法,转换为对象的属性
# 添加为property装饰器以后,我们就可以像调用属性一样使用get方法
@property
def name(self):
print('get方法执行了~~~')
return self._name
# setter方法的装饰器:@属性名.setter
@name.setter
def name(self , name):
print('setter方法调用了')
self._name = name
@property
def age(self):
return self._age
@age.setter
def age(self , age):
self._age = age
p = Person('猪八戒',18)
"""
setter方法调用了
get方法执行了~~~
"""
p.name = '孙悟空'
p.age = 28
print(p.name,p.age) # 孙悟空 28
3.2、继承
继承是面向对象三大特性之一
-
通过继承可以使一个类(子类)获取到其他类(父类)中的属性和方法,避免编写重复性的代码,并且也符合OCP原则
-
在定义类时,可以在类名后的括号中指定当前类的父类(超类、基类、super)
-
issubclass(A, B) 检查一个类(A)是否是另一个类(B)的子类
-
类似的,isinstance(A, B)用来检查一个对象(A)是否是一个类(B)的实例
3.2.1、如何拓展已有的类的功能
# 举例:有一个Animal类,包含两个方法run()、sleep(),能够实现大部分功能,但是不能实现全部功能
# 如何能让这个类来实现全部的功能呢?
# 1、直接修改这个类,在这个类中添加我们需要的功能
# - 修改起来会比较麻烦,并且会违反OCP原则
# 2、直接创建一个新的类
# - 比较麻烦,会出现大量的重复性代码
# 3、直接从Animal类中来继承它的属性和方法
# 定义类 Animal(动物)
# 这个类中有两个方法:run()、sleep()
class Animal:
def run(self):
print('动物会跑~~~')
def sleep(self):
print('动物睡觉~~~')
class Dog(Animal):
def bark(self):
print('汪汪汪~~~')
def run(self):
print('狗跑~~~~')
class Hashiqi(Dog):
def fan_sha(self):
print('我是一只傻傻的哈士奇')
d = Dog()
h = Hashiqi()
d.run() # 狗跑~~~~
d.sleep() # 动物睡觉~~~
d.bark() # 汪汪汪~~~
# issubclass() 检查一个类是否是另一个类的子类
print(issubclass(Animal , Dog)) # False
print(issubclass(Animal , object)) # True
print(issubclass(Person , object)) # True
3.2.2、方法重写
普通方法重写
如果子类中有和父类同名的方法,则通过子类实例调用方法时,会调用子类的方法而不是父类的方法,叫做方法的重写(覆盖,override)
- 调用对象的方法时,会优先去当前对象中寻找是否具有该方法
- 如果有则直接调用;
- 如果没有,则去当前对象的父类中寻找,如果父类中有则调用父类中的方法;
- 如果没有,则去父类的父类中寻找,以此类推,直到找到object,如果依然没有找到,则报错
class Animal:
def run(self):
print('动物会跑~~~')
def sleep(self):
print('动物睡觉~~~')
class Dog(Animal):
def bark(self):
print('汪汪汪~~~')
def run(self):
print('狗跑~~~~')
# 创建Dog类的实例
d = Dog()
d.run() # 狗跑~~~~
class A(object):
def test(self):
print('AAA')
class B(A):
def test(self):
print('BBB')
class C(B):
def test(self):
print('CCC')
# 创建一个c的实例
c = C()
c.test() # CCC
特殊方法重写
- 父类中的所有方法都会被子类继承,包括特殊方法,因此可以重写特殊方法
- 子类中重写父类init()方法
- super() 可以用来获取当前类的父类
class Animal:
def __init__(self,name):
self._name = name
def run(self):
print('动物会跑~~~')
@property
def name(self):
return self._name
@name.setter
def name(self,name):
self._name = name
# 父类中的所有方法都会被子类继承,包括特殊方法,也可以重写特殊方法
class Dog(Animal):
def __init__(self,name,age):
# 在子类的初始化函数中初始化父类的属性
# super() 可以用来获取当前类的父类,通过super()返回对象调用父类方法
super().__init__(name)
# 子类属性初始化
self._age = age
def run(self):
print('狗跑~~~~')
@property
def age(self):
return self._age
@age.setter
def age(self,age):
self._age = age
d = Dog('旺财',18)
print(d.name) # 旺财
print(d.age) # 18
3.3.3、多重继承
python支持多重继承,即可以为一个类同时指定多个父类
- 多重继承的定义形式:classs C(A, B)
- C类(子类)同时拥有两个父类(A、B),并且会获取到父类中的所有方法
- 如果多个父类中有同名的方法,则会现在第一个父类中寻找
- 若找到则调用,否则找第二个,直至object类,始终没找到则报错
- 应该尽量避免使用多重继承,因为多重继承会让我们的代码过于复杂
class A(object):
def test(self):
print('AAA')
class B(object):
def test(self):
print('B中的test()方法~~')
def test2(self):
print('BBB')
class C(A,B):
pass
# 类名.__bases__ 这个属性可以用来获取当前类的所有父类
print(B.__bases__) # (<class 'object'>,)
print(C.__bases__) # (<class '__main__.A'>, <class '__main__.B'>)
c = C()
c.test() # AAA
c.test2() # BBB
3.3、多态
多态是面向对象的三大特征之一
-
多态从字面上理解是一个对象可以有不同的形态:例如动物可以分为狗、猫…
-
多态的概念依赖于继承
class Animal:
def run(self):
print('动物会跑~~~')
def sleep(self):
print('动物睡觉~~~')
class Dog(Animal):
def bark(self):
print('汪汪汪~~~')
def run(self):
print('狗跑~~~~')
class Cat(Animal):
def bark(self):
print('喵喵喵~~~')
def run(self):
print('猫跑~~~~')
a = Dog()
a.bark() # 汪汪汪~~~
b = Cat()
b.bark() # 喵喵喵~~~
多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)
- 每个对象用自己的方式响应共同的消息,所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数
# 定义say_hello()函数,只要对象中含有bark()方法,它就可以作为参数传递
# 这个函数并不会考虑对象的类型
def say_hello(obj):
obj.bark()
a = Dog()
say_hello(a) # 汪汪汪~~~
b = Dog()
say_hello(b) # 喵喵喵~~~
# 在函数中可以对输入变量做类型检查(不常用),例如,只有obj是Dog类型的对象时,才可以正常使用,其他类型的对象都无法使用该函数
# 这个函数就不具备多态性,函数的适应性非常的差
def say_hello_2(obj):
# 做类型检查,isinstance()在很少使用
if isinstance(obj , Dog):
obj.bark()
鸭子类型:如果一个东西,走路像鸭子,叫声像鸭子,那么它就是鸭子
- 在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的
class Duck():
def walk(self):
print('I walk like a duck')
def swim(self):
print('i swim like a duck')
class Person():
def walk(self):
print('this one walk like a duck')
def swim(self):
print('this man swim like a duck')
# Person类拥有跟Duck类一样的方法
# 当有一个函数调用Duck类,并利用到了两个方法walk()和swim(),传入Person类也一样可以运行
# 函数并不会检查对象的类型是不是Duck,只要他拥有walk()和swim()方法,就可以正确的被调用。
再举例,如果一个对象实现了getitem方法,那python的解释器就会把它当做一个collection,就可以在这个对象上使用切片,获取子项等方法;
如果一个对象实现了iter和next方法,python就会认为它是一个iterator,就可以在这个对象上通过循环来获取各个子项。
相关链接:python中对多态的理解 - 宇文哲的文章 - 知乎 https://zhuanlan.zhihu.com/p/88402677
四、其他
4.1、模块(module)
4.1.1、引入模块
模块化指将一个完整的程序分解为一个一个小的模块,通过将模块组合,搭建出一个完整的程序
- 不采用模块化,统一将所有的代码编写到一个文件中
- 采用模块化,将程序分别编写到多个文件中
模块化的优点:方便开发、方便维护、模块可复用
python中一个py文件就是一个模块,要想创建模块,实际上就是创建一个python文件
- 注意:模块名要符号标识符的规范
在一个模块中引入外部模块
① import 模块名 (模块名,就是python文件的名字)
② import 模块名 as 模块别名
-
import可以在程序的任意位置调用,但是一般会统一写在程序的开头
-
可以引入同一个模块多次,但是模块的实例只会创建一个
-
在每一个模块内部都有一个name属性,通过这个属性可以获取到模块的名字
-
name属性值为main的模块是主模块,一个程序中只会有一个主模块
- 主模块就是我们直接通过python执行的模块
import test_module as test
print(test.__name__) # test_module
print(__name__) # __main__
也可以只引入模块中的部分内容
① from 模块名 import 变量,变量…
② from 模块名 import 变量 as 别名
引入模块的所有内容(不建议)
访问模块中的变量:模块名.变量名
4.1.2、包(package)
包也是一个模块
- 普通的模块就是一个py文件,而包是一个文件夹
- 当模块中代码过多时,或者一个模块需要被分解为多个模块时,这时就需要使用到包
- 包中必须要一个一个 init.py文件,这个文件中导入有必要的内容,不要做没必要的运算
- init.py相关内容:init.py的神奇用法 - 酸痛鱼的文章 - 知乎 https://zhuanlan.zhihu.com/p/115350758
4.2、垃圾回收
python中有自动的垃圾回收机制,它会自动将这些没有被引用的对象删除,不用手动处理垃圾回收
class A:
def __init__(self):
self.name = 'A类'
# del是一个特殊方法,它会在对象被垃圾回收前调用
def __del__(self):
print('A()对象被删除了~~~',self)
a = A()
print(a.name) # A类
a = None # 将a设置为了None,此时没有任何的变量对A()对象进行引用,它就是变成了垃圾,所谓的垃圾回收就是将垃圾对象从内存中删除
del a # A()对象被删除了~~~ <__main__.A object at 0x000001BEDE75C5E0>
更多推荐
所有评论(0)