十.进阶面向对象

封装、继承、多态的内容回顾

封装

使用函数来开发,代码如下:

# 创建全局变量
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()

'''
通过类创建一个模板,使用这个模板来创建相关的功能
使用不同的变量接收这个类创建的模板的实体

每一个实体互不干扰,在内存中享有独立的空间
'''

使用面向对象开发的好处

  1. 在使用面向过程编程时,当需要对数据处理的过程中,需要考虑用哪个函数来进行操作,但是当用面向对象编程时,因为已经将数据存储到了这个独立的空间中,这个独立的空间(即对象)中通过一个特殊的变量(__class__)能够获取到类(模板),而且这个类中的方法是有一定数量的,与此类无关的将不会出现在本类中,因此需要对数据处理时,可以很快速的定位到需要的方法是谁 这样更方便
  2. 全局变量是只能有1份的,多个函数需要多个备份时,往往需要利用其它的变量来进行储存;而通过封装 会将用来存储数据的这个变量 变为了对象中的一个“全局”变量,只要对象不一样那么这个变量就可以再有1份,所以这样更方便
  3. 代码划分更清晰
继承

使用继承的特性进行功能开发,代码如下:

# 创建一个类,在这个类中有基础功能
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()

'''
通过上述代码发现 子类只需要继承父类就可以得到父类中的方法,无需重复编写
如果没有继承这种特性的话,那么只能在一个类中编写特别多的功能。在这种情况下无法很好的进行功能升级
'''

继承特性的优点

  1. 能够提升代码的重用率,即开发一个类,可以在多个子功能中直接使用
  2. 继承能够有效的进行代码的管理,当某个类有问题只要修改这个类就行,而其继承这个类的子类往往不需要就修改
多态
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__发生的状态******

上述两种调用父类的方法是有区别的

  1. 如果2个子类中都继承了父类,当在子类中通过父类名调用时,parent被执行了2次
  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__发生的状态******
简单总结
  1. super().__init__相对于类名.__init__,在单继承上用法基本无差
  2. 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果
  3. 多继承时,使用super方法,对父类的传参数,由于super的算法导致的原因,必须把参数全部传递,否则会报错
  4. 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
  5. 多继承时,相对于使用类名.__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便是一个动态语言,除此之外如 PHPRubyJavaScript 等也都属于动态语言,而 C语言 C++ 等语言则不属于动态语言。

在运行中给实例对象绑定(添加)属性
>>> class Person(object):
    def __init__(self, name=None, age=None):
        self.name = name
        self.age = age


>>> p = Person("张三", "20")
>>>

在这里,我们定义了一个类Person,在这个类里,定义了两个初始属性nameage,但是人还有性别啊!如果这个类不是你写的是不是你会尝试访问性别这个属性呢?

>>> 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--
在代码运行中删除属性与方法

删除的方法:

  1. del 对象.属性名
  2. delattr(对象, "属性名")
简单总结
  1. Python可以在运行的过程中,修改程序的运行结构,例如可以修改调用的函数等
  2. 对象中其实一切皆属性,方法其实也是属性,只不过这个可以进行调用而已,例如 实例对象名.属性名()
  3. 相对于动态语言,静态语言具有严谨性!所以,玩动态语言的时候,小心动态的坑!

那么怎么避免这种情况呢? 请使用__slots__

__slots__

动态语言的概念

现在我们终于明白了,动态语言与静态语言的不同

动态语言:可以在运行的过程中,修改代码

静态语言:编译时已经确定好代码,运行过程中不能修改

如果我们想要限制实例的属性怎么办?比如,只允许对Person实例添加nameage属性

使用__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
简单总结
  1. 为了限制随意给对象添加属性,可以使用__slots__来完成
  2. __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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值