封装、继承、多态的内容回顾
封装
使用函数来开发,代码如下:
# 创建全局变量
int_list = list()
int_set = set()
def work_1():
pass
def work_2():
pass
def work_3():
pass
'''
实际开发中,为了完成较为复杂的任务往往需要多个函数进行配合使用
当一个函数中收到了数据,为了让其他函数能够直接使用,很多人想到了使用全局变量来实现数据传递的功能
但是这种做法也有不好的地方:并发程序对同一个全局变量进行操作,产生资源竞争的问题
'''
使用类来开发
class Work:
def __init__(self):
self.int_list = list()
self.int_set = set()
def work_1(self):
pass
def work_2(self):
pass
def work_3(self):
pass
obj_1 = Work()
obj_2 = Work()
'''
通过类创建一个模板,使用这个模板来创建相关的功能
使用不同的变量接收这个类创建的模板的实体
每一个实体互不干扰,在内存中享有独立的空间
'''
使用面向对象开发的好处
- 在使用面向过程编程时,当需要对数据处理的过程中,需要考虑用哪个函数来进行操作,但是当用面向对象编程时,因为已经将数据存储到了这个独立的空间中,这个独立的空间(即对象)中通过一个特殊的变量(
__class__
)能够获取到类(模板),而且这个类中的方法是有一定数量的,与此类无关的将不会出现在本类中,因此需要对数据处理时,可以很快速的定位到需要的方法是谁 这样更方便 - 全局变量是只能有1份的,多个函数需要多个备份时,往往需要利用其它的变量来进行储存;而通过封装 会将用来存储数据的这个变量 变为了对象中的一个“全局”变量,只要对象不一样那么这个变量就可以再有1份,所以这样更方便
- 代码划分更清晰
继承
使用继承的特性进行功能开发,代码如下:
# 创建一个类,在这个类中有基础功能
class Camera:
def take_photo(self):
"""拍照功能"""
print("正在拍照...")
class Telephone(Camera):
def call(self):
"""打电话"""
print("正在打电话...")
def answer(self):
"""接电话"""
print("正在接电话...")
phone = Telephone()
phone.call()
phone.answer()
phone.take_photo()
'''
通过上述代码发现 子类只需要继承父类就可以得到父类中的方法,无需重复编写
如果没有继承这种特性的话,那么只能在一个类中编写特别多的功能。在这种情况下无法很好的进行功能升级
'''
继承特性的优点
- 能够提升代码的重用率,即开发一个类,可以在多个子功能中直接使用
- 继承能够有效的进行代码的管理,当某个类有问题只要修改这个类就行,而其继承这个类的子类往往不需要就修改
多态
class MiniOS(object):
"""MiniOS 操作系统类 """
def __init__(self, name):
self.name = name
self.apps = [] # 安装的应用程序名称列表
def __str__(self):
return "%s 安装的软件列表为 %s" % (self.name, str(self.apps))
def install_app(self, app):
# 判断是否已经安装了软件
if app.name in self.apps:
print("已经安装了 %s,无需再次安装" % app.name)
else:
app.install()
self.apps.append(app.name)
class App(object):
def __init__(self, name, version, desc):
self.name = name
self.version = version
self.desc = desc
def __str__(self):
return "%s 的当前版本是 %s - %s" % (self.name, self.version, self.desc)
def install(self):
print("将 %s [%s] 的执行程序复制到程序目录..." % (self.name, self.version))
class PyCharm(App):
pass
class Chrome(App):
def install(self):
print("正在解压缩安装程序...")
super().install()
linux = MiniOS("Linux")
print(linux)
pycharm = PyCharm("PyCharm", "1.0", "python 开发的 IDE 环境")
chrome = Chrome("Chrome", "2.0", "谷歌浏览器")
linux.install_app(pycharm)
linux.install_app(chrome)
linux.install_app(chrome)
print(linux)
简单总结
-
面向过程开发,简单、开发前期快速,越往后越复杂,适合小工程
-
面向对象开发,复杂、开发前期较慢,越往后开发越方便,适合大工程
没有最好的开发模式,只有经过多多练习,见的多了,感受多了,自然也就能够在不同的任务、不同的工程,使用合适的方式进行开发
静态方法与类方法
类属性与实例属性
它们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
- 实例属性属于对象
- 类属性属于类
class Province(object):
# 类属性
country = '中国'
def __init__(self, name):
# 实例属性
self.name = name
# 创建一个实例对象
obj = Province('湖南省')
# 直接访问实例属性
print(obj.name)
# 直接访问类属性
Province.country
由上述代码可以看出【实例属性需要通过对象来访问】【类属性通过类访问】,在使用上可以看出实例属性和类属性的归属是不同的。
类属性与实例属性在内存中的保存方式:
- 类属性在内存中只保存一份
- 实例属性在每个对象中都要保存一份
应用场景:
- 通过类创建实例对象时,如果每个对象需要具有相同名字的属性,那么就使用类属性,用一份既可
实例方法、静态方法与类方法
方法包括:实例方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 实例方法:由对象调用;至少一个self参数;执行实例方法时,自动将调用该方法的对象赋值给self
- 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类赋值给cls
- 静态方法:由类调用;无默认参数
class Foo(object):
def __init__(self, name):
self.name = name
def ord_func(self):
""" 定义实例方法,至少有一个self参数 """
# print(self.name)
print('实例方法')
@classmethod
def class_func(cls):
""" 定义类方法,至少有一个cls参数 """
print('类方法')
@staticmethod
def static_func():
""" 定义静态方法 ,无默认参数"""
print('静态方法')
f = Foo("中国")
# 调用实例方法
f.ord_func()
# 调用类方法
Foo.class_func()
# 调用静态方法
Foo.static_func()
方法对比
- 相同点:对于所有的方法而言,均属于类,所以 在内存中也只保存一份
- 不同点:方法调用者不同、调用方法时自动传入的参数不同。
多继承以及MRO顺序
多继承中调用父类方式不同结果不同
单独调用父类的方法
print("******多继承使用类名.__init__发生的状态******")
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
Parent.__init__(self, name)
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender):
print('Son2的init开始被调用')
self.gender = gender
Parent.__init__(self, name)
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
Son1.__init__(self, name, age) # 单独调用父类的初始化方法
Son2.__init__(self, name, gender)
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用类名.__init__发生的状态******\n\n")
运行结果:
******多继承使用类名.__init__发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用类名.__init__发生的状态******
多继承中
super
调用被重写的父类方法
print("******多继承使用super().__init__发生的状态******")
class Parent(object):
def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init开始被调用')
self.age = age
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init开始被调用')
self.gender = gender
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
# 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
# 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
# super(Grandson, self).__init__(name, age, gender)
super().__init__(name, age, gender)
print('Grandson的init结束被调用')
print(Grandson.__mro__)
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用super().__init__发生的状态******\n\n")
运行结果:
******多继承使用super().__init__发生的状态******
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用super().__init__发生的状态******
上述两种调用父类的方法是有区别的
- 如果2个子类中都继承了父类,当在子类中通过父类名调用时,parent被执行了2次
- 如果2个子类中都继承了父类,当在子类中通过super调用时,parent被执行了1次
单继承中的super
print("******单继承使用super().__init__发生的状态******")
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
super().__init__(name) # 单继承不能提供全部参数
print('Son1的init结束被调用')
class Grandson(Son1):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
self.gender = gender
super().__init__(name, age) # 单继承不能提供全部参数
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******单继承使用super().__init__发生的状态******\n\n")
运行结果:
******单继承使用super().__init__发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******单继承使用super().__init__发生的状态******
简单总结
super().__init__
相对于类名.__init__
,在单继承上用法基本无差- 但在多继承上有区别,
super
方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果 - 多继承时,使用
super
方法,对父类的传参数,由于super
的算法导致的原因,必须把参数全部传递,否则会报错 - 单继承时,使用
super
方法,则不能全部传递,只能传父类方法所需的参数,否则会报错 - 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
面试题
以下代码将会输出什么?
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)
输出结果:
1 1 1
1 2 1
3 2 3
使你困惑或是惊奇的是关于最后一行的输出是 3 2 3 而不是 3 2 1。为什么改变了 Parent.x 的值还会改变 Child2.x 的值,但是同时 Child1.x 值却没有改变?
这个答案的关键是,在 Python 中,类变量在内部是作为字典处理的。如果一个变量的名字没有在当前类的字典中发现,将搜索祖先类(比如父类)直到被引用的变量名被找到(如果这个被引用的变量名既没有在自己所在的类又没有在祖先类中找到,会引发一个 AttributeError 异常 )。
因此,在父类中设置 x = 1 会使得类变量 x 在引用该类和其任何子类中的值为 1。这就是因为第一个 print 语句的输出是 1 1 1。
随后,如果任何它的子类重写了该值(例如,我们执行语句 Child1.x = 2),然后,该值仅仅在子类中被改变。这就是为什么第二个 print 语句的输出是 1 2 1。
最后,如果该值在父类中被改变(例如,我们执行语句 Parent.x = 3),这个改变会影响到任何未重写该值的子类当中的值(在这个示例中被影响的子类是 Child2)。这就是为什么第三个 print 输出是 3 2 3。
内建属性
什么是内建属性
往往是指我们在使用类时可以直接使用的那些功能,例如__new__
、__init__
等
新式类
Python3 中定义的类都是新式类的,无论是否写明一个类继承object
,都会间接或直接继承object
class Person(object):
pass
Python3 类的内建属性和方法
In [2]: dir(Person)
Out[2]:
['__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__']
旧式类
# Python2中无继承父类,称之经典类。Python3中已默认继承object
class Person:
pass
Python 2.7.16 (default, Mar 25 2021, 18:52:10)
[GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.37.14)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class Person:
... pass
...
>>> dir(Person)
['__doc__', '__module__']
>>>
常用内建属性
常用专有属性 | 说明 | 触发方式 |
---|---|---|
__init__ | 构造初始化函数 | 创建实例后,赋值时使用,在__new__ 后自动调用 |
__new__ | 生成实例所需属性 | 创建实例时 |
__class__ | 实例所在的类 | 实例.__class__ |
__str__ | 实例字符串表示,可读性 | print(类实例) 如没实现,使用repr结果 |
__repr__ | 实例字符串表示,准确性 | 类实例 回车 或者 print(repr(类实例)) |
__del__ | 析构(对象被删除前做清理工作) | del 实例 后,如果对象引用计数为0,则自动调用 |
__dict__ | 实例自定义属性 | vars(实例.__dict__) |
__doc__ | 类文档,子类不继承 | help(类或实例) |
__getattribute__ | 属性访问拦截器 | 访问实例属性时 |
__bases__ | 类的所有父类构成元素 | 类名.__bases__ |
__getattribute__
属性
__getattribute__
功能很强大:能够完成属性访问时进行拦截
class Tuling(object):
def __init__(self,subject1):
self.subject1 = subject1
self.subject2 = 'cpp'
# 属性访问时拦截器,打log
def __getattribute__(self,obj):
if obj == 'subject1':
print('log subject1')
return 'redirect python'
else: # 测试时注释掉这2行,将找不到subject2
return object.__getattribute__(self,obj)
def show(self):
print('this is Tuling_Class')
s = Tuling("python")
print(s.subject1)
print(s.subject2)
运行结果:
log subject1
redirect python
cpp
__getattribute__
注意事项
class Person(object):
def __getattribute__(self, obj):
print("---test---")
if obj.startswith("a"):
return "hahha"
else:
return self.test
def test(self):
print("heihei")
p = Person()
print(p.a) # 返回hahha
print(p.b) # 会让程序死掉
# 原因是:当t.b执行时,会调用Person类中定义的__getattribute__方法,但是在这个方法的执行过程中
# if条件不满足,所以 程序执行else里面的代码,即return self.test 问题就在这,因为return 需要把
# self.test的值返回,那么首先要获取self.test的值,因为self此时就是p这个对象,所以self.test就是
# p.test 此时要获取p这个对象的test属性,那么就会跳转到__getattribute__方法去执行,即此时产
# 生了递归调用,由于这个递归过程中 没有判断什么时候退出,所以这个程序会永无休止的运行下去,又因为
# 每次调用函数,就需要保存一些数据,那么随着调用的次数越来越多,最终内存吃光,所以程序 崩溃
#
# 注意:以后不要在__getattribute__方法中调用self.xxxx
魔法属性
无论人或事物往往都有不按套路出牌的情况,Python的类属性也是如此,存在着一些具有特殊含义的属性,详情如下
__doc__
- 表示类的描述信息
class Foo:
""" 描述类信息,这是一个类的简单描述 """
def func(self):
pass
print(Foo.__doc__)
# 输出:类的描述信息
__module__
和__class__
- module 表示当前操作的对象在那个模块
- class 表示当前操作的对象的类是什么
test.py
class Person(object):
def __init__(self):
self.name = '张三'
main.py
from test import Person
obj = Person()
print(obj.__module__) # 输出 test 即:输出模块
print(obj.__class__) # 输出 test.Person 即:输出类
__init__
- 初始化方法,通过类创建对象时,自动触发执行
class Person:
def __init__(self, name):
self.name = name
self.age = 18
obj = Person('张三') # 自动执行类中的 __init__ 方法
__del__
- 当对象在内存中被释放之前,自动触发执行
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以__del__
的调用是由解释器在进行垃圾回收前 自动触发,执行一些完善工作
class Foo:
def __del__(self):
pass
__call__
- 对象后面加括号,触发执行。
注:__init__
方法的执行是由创建对象触发的,即:对象 = 类名()
;而对于 __call__
方法的执行是由对象后加括号触发的,即:对象()
或者 类()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
__dict__
- 类或对象中的所有属性
类的实例属性属于对象;类中的类属性和方法等属于类,即:
class Province(object):
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}
obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}
obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}
__str__
- 如果一个类中定义了
__str__
方法,那么在打印对象时,默认输出该方法的返回值。
class Foo:
def __str__(self):
return '双双'
obj = Foo()
print(obj)
# 输出:双双
__getitem__
、__setitem__
、__delitem__
- 用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo(object):
def __getitem__(self, key):
print('__getitem__', key)
def __setitem__(self, key, value):
print('__setitem__', key, value)
def __delitem__(self, key):
print('__delitem__', key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = '张三' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__
动态绑定
动态语言的定义
动态编程语言 是 高级程序设计语言 的一个类别,在计算机科学领域已被广泛应用。它是一类 在运行时可以改变其结构的语言
例如:新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。
动态语言目前非常具有活力。例如Python
便是一个动态语言,除此之外如 PHP
、 Ruby
、 JavaScript
等也都属于动态语言,而 C语言
、 C++
等语言则不属于动态语言。
在运行中给实例对象绑定(添加)属性
>>> class Person(object):
def __init__(self, name=None, age=None):
self.name = name
self.age = age
>>> p = Person("张三", "20")
>>>
在这里,我们定义了一个类Person
,在这个类里,定义了两个初始属性name
和age
,但是人还有性别啊!如果这个类不是你写的是不是你会尝试访问性别这个属性呢?
>>> p.sex = "女"
>>> p.sex
'女'
>>>
这时候就发现问题了,我们定义的类里面没有sex
这个属性啊!怎么回事呢? 这就是动态语言的魅力和坑!
这里实际上就是动态给实例绑定属性!
在运行中给类绑定(添加)属性
>>> p1 = Person("双双", "25")
>>> p1.sex
Traceback (most recent call last):
File "<pyshell#21>", line 1, in <module>
p1.sex
AttributeError: Person instance has no attribute 'sex'
>>>
我们尝试打印p1.sex
,发现报错,p1
没有sex
这个属性!—— 给p
这个实例绑定属性对p1
这个实例不起作用! 那我们要给所有的Person
的所有实例加上 sex
属性怎么办呢?
答案就是直接给Person
类绑定属性!
>>>> Person.sex = None # 给类Person添加一个属性
>>> p2 = Person("双双", "25")
>>> print(p2.sex) # 如果p2这个实例对象中没有sex属性的话,那么就会访问它的类属性
None # 可以看到没有出现异常
>>>
运行中给实例对象绑定(添加)方法
我们直接给Person
绑定sex
这个属性,然后实例化p2
后,p2
就有sex
这个属性了! 那么方法呢?怎么绑定?
>>> class Person(object):
def __init__(self, name=None, age=None):
self.name = name
self.age = age
def eat(self):
print("eat food")
>>> def run(self, speed):
print("%s在移动, 速度是%dkm/h" % (self.name, speed))
>>> p3 = Person("李四", 24)
>>> p3.eat()
eat food
>>>
>>> p3.run()
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
p3.run()
AttributeError: Person instance has no attribute 'run'
>>>
>>>
>>> import types
>>> p3.run = types.MethodType(run, p3)
>>> p3.run(180)
夏洛在移动,速度是180km/h
完整代码示例
import types
# 定义了一个类
class Person(object):
num = 0
def __init__(self, name = None, age = None):
self.name = name
self.age = age
def eat(self):
print("---默认的实例方法---")
# 定义一个实例方法
def run(self, speed):
print("----实例方法--1--")
print("%s在移动, 速度是 %d km/h"%(self.name, speed))
print("----实例方法--2--")
# 定义一个类方法
@classmethod
def test_class(cls):
print("----类方法--1--")
print("num=%d" % cls.num)
cls.num = 100
print("num=%d" % cls.num)
print("----类方法--2--")
# 定义一个静态方法
@staticmethod
def test_static():
print("----静态方法--1--")
print("---static method----")
print("----静态方法--2--")
# 创建一个实例对象
p = Person("张三", 18)
# 调用在class中的方法
p.eat()
# 给这个对象添加实例方法
p.run = types.MethodType(run, p)
# 调用实例方法
p.run(180)
# 给Person类绑定类方法
Person.test_class = test_class
# 调用类方法
Person.test_class()
# 给Person类绑定静态方法
Person.test_static = test_static
# 调用静态方法
Person.test_static()
运行结果:
---默认的实例方法---
----实例方法--1--
张三在移动, 速度是 180 km/h
----实例方法--2--
----类方法--1--
num=0
num=100
----类方法--2--
----静态方法--1--
---static method----
----静态方法--2--
在代码运行中删除属性与方法
删除的方法:
del 对象.属性名
delattr(对象, "属性名")
简单总结
- Python可以在运行的过程中,修改程序的运行结构,例如可以修改调用的函数等
- 对象中其实一切皆属性,方法其实也是属性,只不过这个可以进行调用而已,例如
实例对象名.属性名()
- 相对于动态语言,静态语言具有严谨性!所以,玩动态语言的时候,小心动态的坑!
那么怎么避免这种情况呢? 请使用__slots__
__slots__
动态语言的概念
现在我们终于明白了,动态语言与静态语言的不同
动态语言:可以在运行的过程中,修改代码
静态语言:编译时已经确定好代码,运行过程中不能修改
如果我们想要限制实例的属性怎么办?比如,只允许对Person
实例添加name
和age
属性
使用__slots__
限制属性的创建
为了达到限制的目的,Python
允许在定义类的时候,定义一个特殊的__slots__
属性,来限制该类创建的实例对象可以添加的属性
>>> class Person(object):
__slots__ = ("name", "age")
>>> P = Person()
>>> P.name = "teacher" # 可以执行
>>> P.age = 20 # 可以执行
>>> P.score = 100 # 执行失败
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
AttributeError: Person instance has no attribute 'score'
>>>
注意事项
- 使用
__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
In [67]: class Test(Person):
...: pass
...:
In [68]: t = Test()
In [69]: t.score = 100
简单总结
- 为了限制随意给对象添加属性,可以使用
__slots__
来完成 __slots__
对子类不起作用
property
属性
@property
什么是property属性?
一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法。
class Foo:
def func(self):
pass
# 定义property属性
@property
def prop(self):
pass
foo_obj = Foo()
foo_obj.func() # 调用实例方法
foo_obj.prop # 调用property属性
class Goods:
@property
def money(self):
return 100
goods = Goods()
print(goods.money)
property属性的定义和调用要注意一下几点:
- 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
- 调用时,无需括号
简单的示例
对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出 m 和 n
- 根据m 和 n 去数据库中请求数据
class Pager:
def __init__(self, current_page):
# 用户当前请求的页码(第一页、第二页...)
self.current_page = current_page
# 每页默认显示10条数据
self.per_items = 10
@property
def start(self):
val = (self.current_page - 1) * self.per_items
return val
@property
def end(self):
val = self.current_page * self.per_items
return val
# ############### 调用 ###############
p = Pager(1)
print(p.start) # 就是起始值,即:m
print(p.end) # 就是结束值,即:n
- Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。
property属性的两种方式
- 装饰器 即:在方法上应用装饰器
- 类属性 即:在类中定义值为property对象的类属性
装饰器方式
在类的实例方法上应用@property装饰器
Python中的类有经典类
和新式类
,新式类
的属性比经典类
的属性丰富。( 如果类继object,那么该类是新式类 )
经典类,具有一种@property装饰器:
class Goods:
@property
def price(self):
return 100
obj = Goods()
result = obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
print(result)
新式类,具有三种@property装饰器
class Goods(object):
@property
def price(self):
print('@property')
@price.setter
def price(self, value):
print('@price.setter')
@price.deleter
def price(self):
print('@price.deleter')
obj = Goods()
print(obj.price) # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123 # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数
del obj.price # 自动执行 @price.deleter 修饰的 price 方法
注意:
- 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
- 新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object):
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
print(obj.price) # 获取商品价格
obj.price = 200 # 修改商品原价
print(obj.price)
del obj.price # 删除商品原价
# 当前属性被删除之后再获取则报错
# print(obj.price)
类属性方式,创建值为property对象的类属性
- 当使用类属性的方式创建property属性时,
经典类
和新式类
无区别
class Goods:
def get_price(self):
return 100
price = property(get_price)
obj = Goods()
result = obj.price # 自动调用get_price方法,并获取方法的返回值
print(result)
property方法中有个四个参数
- 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
- 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
- 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
- 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息
class Foo(object):
def get_bar(self):
print("getter...")
return 'a...'
def set_bar(self, value):
"""必须两个参数"""
print("setter:", value)
return 'set value' + value
def del_bar(self):
print("deleter...")
return 'b...'
BAR = property(get_bar, set_bar, del_bar, "description...")
obj = Foo()
print(obj.BAR) # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "c" # 自动调用第二个参数中定义的方法:set_bar方法,并将“c”当作参数传入
desc = Foo.BAR.__doc__ # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR # 自动调用第三个参数中定义的方法:del_bar方法
由于类属性方式
创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object):
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
def get_price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
def set_price(self, value):
self.original_price = value
def del_price(self):
del self.original_price
PRICE = property(get_price, set_price, del_price, '价格属性描述...')
obj = Goods()
print(obj.PRICE) # 获取商品价格
obj.PRICE = 200 # 修改商品原价
print(obj.PRICE)
del obj.PRICE # 删除商品原价
@property - 应用
- 私有属性添加getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
def get_money(self):
return self.__money
def set_money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
money = Money()
print(money.get_money())
money.set_money(10)
print(money.get_money())
- 使用property升级getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
def get_money(self):
return self.__money
def set_money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
# 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoney
money = property(get_money, set_money)
money_obj = Money()
money_obj.money = 100 # 调用setMoney方法
print(money_obj.money) # 调用getMoney方法
- 使用property取代getter和setter方法
- 重新实现一个属性的设置和读取方法,可做边界判定
class Money(object):
def __init__(self):
self.__money = 0
# 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
@property
def money(self):
return self.__money
# 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
@money.setter
def money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
money_obj = Money()
money_obj.money = 100
print(money_obj.money)