热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

面向对象高级开发

面向对象高级开发文章目录面向对象高级开发1.概述2.python语言面向对象特性2.1.类成员介绍1.私有属性2.从字典中获取对象内容3.内置类方法装饰器4.属性装饰器2.2.抽象




面向对象高级开发

文章目录


  • 面向对象高级开发
    • 1.概述
    • 2.python语言面向对象特性
      • 2.1.类成员介绍
        • 1.私有属性
        • 2.从字典中获取对象内容
        • 3.内置类方法装饰器
        • 4.属性装饰器

      • 2.2.抽象类
        • 1.抽象类的子类化机制






1.概述

这篇文章总结python语言面向对象一些设计思想以及使用面向对象思想去设计一个对象。


2.python语言面向对象特性


2.1.类成员介绍

在Python中,类(class)是我们实践面向对象编程时最重要的工具之一。通过类,我们可以把头脑中的抽象概念进行建模,进而实现复杂的功能。同函数一样,类的语法本身也很简单,但藏着许多值得注意的细节。


1.私有属性

封装(encapsulation)是面向对象编程里的一个重要概念,为了更好地体现类的封装性,许多编程语言支持将属性设置为公开或私有,只是方式略有不同。比如在Java里,我们可以用public和private关键字来表达是否私有;而在Go语言中,公有/私有则是用首字母大小写来区分的

在Python里,所有的类属性和方法默认都是公开的,不过你可以通过添加双下划线前缀__的方式把它们标示为私有。举个例子:

class Foo:
def __init__(self):
self.__bar = 'baz'

上面代码中Foo类的bar就是一个私有属性,如果你尝试从外部访问它,程序就会抛出异常

foo = Foo()
print(foo._bar)
# 输出结果
AttributeError: 'Foo' object has no attribute '_bar'

虽然上面是设置私有属性的标准做法,但Python里的私有只是一个“君子协议”。虽然用属性的本名访问不了私有属性,但只要稍微调整一下名字,就可以继续操作__bar了

foo = Foo()
print(foo._Foo__bar)
# 输出结果
bar

原理说明: 当使用双下划线前缀__的方式把他们标为私有后,python其实是为他们起了一个别名,只有通过这个别名才能访问他们,间接实现了私有化。例如上面_bar属性的别名就是_Foo__bar,访问别名依旧可以输出_bar属性的值。

python为私有成员创建别名格式:Python解释器只是重新给了它一个包含当前类名的别名(_{class}__{var}),因此你仍然可以在外部用这个别名来访问和修改它。

而在日常编程中,我们极少使用双下划线来标示一个私有属性。这是因为“标准”的双下划线前缀,可能会在子类想要重写父类私有属性时带来不必要的麻烦。如果你认为某个属性是私有的,直接给它加上单下划线_前缀就够了。注意单下划线python是不会为他创建别名,因此可以直接访问。

class Foo:
name = 'zhansan'
def __init__(self):
# 单下划线
self._bar = 'bar'
foo = Foo()
# 可以直接访问单下划线属性,说明它实质上不是私有。
print(foo._bar)
# 输出结果
bar

2.从字典中获取对象内容

Python语言内部大量使用了字典类型,比如一个类实例的所有成员,其实都保存在了一个名为__dict__的字典属性中。而且,不光实例有这个字典,类其实也有这个字典。

class Person:
'''
Person类
'''

def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
'''
输出内容
:return:
'''

print(f"Hi, My name is {self.name}, I'm {self.age}")

查看实例和类的字典

p = Person('张三', 100)
# 对象的字典保存当前实例所有的属性
print(p.__dict__)
# 输出结果
{'name': '张三', 'age': 100}
# 类的字典保存类的注释文档、方法等数据
print(Person.__dict__)
#输出结果
{&#39;__module__&#39;: &#39;__main__&#39;, &#39;__doc__&#39;: &#39;\n Person类\n &#39;, &#39;__init__&#39;: <function Person.__init__ at 0x10f566290>, &#39;say&#39;: <function Person.say at 0x10f5663b0>, &#39;__dict__&#39;: <attribute &#39;__dict__&#39; of &#39;Person&#39; objects>, &#39;__weakref__&#39;: <attribute &#39;__weakref__&#39; of &#39;Person&#39; objects>}

我们可以使用这个dict完成一些特殊任务&#xff0c;比如下面的例子。
将字典的内容赋值到上面Person对象的属性&#xff0c;常规的做法是遍历字典&#xff0c;然后将值赋值给Person对象属性。

d &#61; {&#39;name&#39;: &#39;张三&#39;, &#39;age&#39;: 20}
for key, value in d.items():
setattr(p, key, value)
print(f&#39;输出Person对象属性值 name: {p.name}, age:{p.age}&#39;)
#输出结果
输出Person对象属性值 name: 张三, age:20

使用直接修改实例的__dict__属性来快速达到目的

d &#61; {&#39;name&#39;: &#39;张三&#39;, &#39;age&#39;: 20}
p.__dict__.update(d)
print(f&#39;输出Person对象属性值 name: {p.name}, age:{p.age}&#39;)
#输出结果
输出Person对象属性值 name: 张三, age:20

虽然两种方式都可以实现向Person对象赋值属性&#xff0c;但他们是有区别的&#xff0c;通过类属性设置行为可以通过定义__setattr__魔法方法修改&#xff0c;而dict方式赋值则不会受__setattr__魔法限制。下面通过一个例子介绍他们的区别。

class Person:
def __init__(self, name, age):
self.name &#61; name
self.age &#61; age
def __setattr__(self, name, value):
# 不允许设置年龄小于0
if name &#61;&#61; &#39;age&#39; and value < 0:
raise ValueError(f&#39;Invalid age value: {value}&#39;)
super().__setattr__(name, value)

在上面的代码里&#xff0c;Person类增加了__setattr__方法&#xff0c;实现了对age值的校验逻辑。通过对象赋值小于0将会抛出异常。

p &#61; Person(&#39;张三&#39;, 1)
p.age &#61; -1
# 输出结果
ValueError: Invalid age value: -1

虽然普通的属性赋值会被__setattr__限制&#xff0c;但如果你直接操作实例的__dict__字典&#xff0c;就可以无视这个限制&#xff1a;

p &#61; Person(&#39;张三&#39;, 1)
p.__dict__[&#39;age&#39;] &#61; -1
print(f&#39;输出age:{p.age}&#39;)
#输出结果
输出age:-1

3.内置类方法装饰器

在编写类时&#xff0c;除了普通方法以外&#xff0c;我们还常常会用到一些特殊对象&#xff0c;比如类方法、静态方法等。要定义这些对象&#xff0c;得用到特殊的装饰器。下面简单介绍这些装饰器。

类方法&#64;classmethod
当你用def在类里定义一个函数时&#xff0c;这个函数通常称作方法。调用方法需要先创建一个类实例&#xff0c;由示例调用方法。如果你不使用实例&#xff0c;而是直接用类来调用quack()&#xff0c;程序就会因为找不到类实例而报错。

不过&#xff0c;虽然普通方法无法通过类来调用&#xff0c;但你可以用&#64;classmethod装饰器定义一种特殊的方法&#xff1a;类方法&#xff08;class method&#xff09;&#xff0c;它的生命周期属于整个类不在是对象&#xff0c;因此无须实例化也可调用。

class Duck:
def __init__(self, color):
self.color &#61; color
def quack(self):
print(f"Hi, I&#39;m a {self.color} duck!")
&#64;classmethod
def create_random(cls):
"""创建一只随机颜色的鸭子"""
color &#61; random.choice([&#39;yellow&#39;, &#39;white&#39;, &#39;gray&#39;])
# 通过cls创建类对象&#xff0c;并返回该对象
return cls(color&#61;color)

上面create_random方法通过类装饰器定义为类方法&#xff0c;下面通过类来调用该方法。

# 通过类调用类方法&#xff0c;返回类对象
duck &#61; Duck.create_random()
# 通过对象调用普通方法
duck.quack()
# 输出结果
Hi, I&#39;m a yellow duck!

作为一种特殊方法&#xff0c;类方法最常见的使用场景&#xff0c;就是像上面一样定义工厂方法来生成新实例。类方法的主角是类本身&#xff0c;当你发现某个行为不属于实例&#xff0c;而是属于整个类时&#xff0c;可以考虑使用类方法。

静态方法&#64;staticmethod


  • 静态方法不接收当前实例作为第一个位置参数
  • 静态方法可以使用对象和类调用

class Cat:
def __init__(self, name):
self.name &#61; name
def say(self):
sound &#61; self.get_sound()
print(f&#39;{self.name}: {sound}...&#39;)
&#64;staticmethod
# 静态方法不接收当前实例作为第一个位置参数
def get_sound():
repeats &#61; random.randrange(1, 10)
return &#39; &#39;.join([&#39;Meow&#39;] * repeats)

上面get_sound方法定义为静态方法&#xff0c;下面通过对象和类来调用它。

cat &#61; Cat(&#39;波斯猫&#39;)
# 通过对象调用静态方法
cat.say()
# 输出结果
波斯猫: Meow Meow Meow Meow Meow Meow Meow...
# 通过类调用静态方法
print(Cat.get_sound())
# 输出结果
Meow Meow Meow Meow Meow Meow Meow Meow

和普通方法相比&#xff0c;静态方法不需要访问实例的任何状态&#xff0c;是一种与状态无关的方法&#xff0c;因此静态方法其实可以改写成脱离于类的外部普通函数。

选择静态方法还是普通函数&#xff0c;可以从以下几点来考虑&#xff1a;


  • 如果静态方法特别通用&#xff0c;与类关系不大&#xff0c;那么把它改成普通函数可能会更好&#xff1b;
  • 如果静态方法与类关系密切&#xff0c;那么用静态方法更好&#xff1b;
  • 相比函数&#xff0c;静态方法有一些先天优势&#xff0c;比如能被子类继承和重写等。

4.属性装饰器

在一个类里&#xff0c;属性和方法有着不同的职责&#xff1a;属性代表状态&#xff0c;方法代表行为。二者对外的访问接口也不一样&#xff0c;属性可以通过inst.attr的方式直接访问&#xff0c;而方法需要通过inst.method()来调用

不过&#xff0c;&#64;property装饰器模糊了属性和方法间的界限&#xff0c;使用它&#xff0c;你可以把方法通过属性的方式暴露出来。

class Attrs:
def __init__(self,name):
self.name &#61; name
&#64;property
def set_name(self):
return self.name &#43; &#39;非常棒&#39;

使用&#64;property装饰器将set_name方法定义为属性&#xff0c;下面调用该属性查看结果。

att &#61; Attrs(&#39;张三&#39;)
print(att.set_name)
# 输出结果
张三非常棒

&#64;property除了可以定义属性的读取逻辑外&#xff0c;还支持自定义写入和删除逻辑

class UpdateAttr:
def __init__(self, name):
self.name &#61; name
&#64;property
def set_name(self):
return self.name
# 经过&#64;property的装饰以后&#xff0c;set_name 已经从一个普通方法变成了property对象&#xff0c;因此这里可以使用 set_name.setter
# 定义setter方法&#xff0c;该方法会在对属性赋值时被调用
&#64;set_name.setter
def set_name(self, desc):
pass
# 定义deleter方法&#xff0c;该方法会在删除属性时被调用
&#64;set_name.deleter
def set_name(self):
raise RuntimeError(&#39;Can not delete name&#39;)

调用结果

update_attr &#61; UpdateAttr(&#39;张三&#39;)
# 为属性赋值
att &#61; update_attr.set_name &#61; &#39;非常棒&#39;
print(att)
# 输出结果
非常棒
# 删除属性
del update_attr.set_name
# 输出结果
RuntimeError: Can not delete name

&#64;property是个非常有用的装饰器&#xff0c;它让我们可以基于方法定义类属性&#xff0c;精确地控制属性的读取、赋值和删除行为&#xff0c;灵活地实现动态属性等功能。

当你决定把某个方法改成属性后&#xff0c;它的使用接口就会发生很大的变化。你需要学会判断&#xff0c;方法和属性分别适合什么样的场景。举个例子&#xff0c;假如你的类有个方法叫get_latest_items()&#xff0c;调用它会请求外部服务的数十个接口&#xff0c;耗费5&#xff5e;10秒钟。那么这时&#xff0c;盲目把这个方法改成.latest_items属性就不太恰当。人们在读取属性时&#xff0c;总是期望能迅速拿到结果&#xff0c;调用方法则不一样——快点儿慢点儿都无所谓。让自己设计的接口符合他人的使用预期&#xff0c;也是写代码时很重要的一环。


2.2.抽象类


1.抽象类的子类化机制

在介绍抽象类的子类化机制前&#xff0c;先看一个与他相关的例子。

class ThreeFactory:
"""在被迭代时不断返回 3
:param repeat: 重复次数
"""

def __init__(self, repeat):
self.repeat &#61; repeat
def __iter__(self):
for _ in range(self.repeat):
yield 3
tf &#61; ThreeFactory(2)
for i in tf:
print(i)

ThreeFactory是个非常简单的类&#xff0c;它所做的&#xff0c;就是迭代时不断返回数字3&#xff0c;在collections.abc模块中&#xff0c;有许多和容器相关的抽象类&#xff0c;比如代表集合的Set、代表序列的Sequence等&#xff0c;其中有一个最简单的抽象类&#xff1a;Iterable&#xff0c;它表示的是可迭代类型。假如你用isinstance()函数对上面的ThreeFactory实例做类型检查&#xff0c;会得到一个有趣的结果&#xff1a;

print(isinstance(ThreeFactory(2), Iterable))
# 输出结果
True

虽然ThreeFactory没有继承Iterable类&#xff0c;但当我们用isinstance()检查它是否属于Iterable类型时&#xff0c;结果却是True&#xff0c;这正是受了抽象类的特殊子类化机制的影响。

下面通过一个示例来实现上面子类化功能&#xff0c;通过该示例介绍子类化机制。

from abc import ABC
# 要定义一个抽象类&#xff0c;你需要继承ABC类或使用abc.ABCMeta元类
class Validator(ABC):
"""校验器抽象类"""
&#64;classmethod
def __subclasshook__(cls, C):
"""任何提供了validate 方法的类&#xff0c;都被当作 Validator 的子类"""
if any("validate" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
def validate(self, value):
raise NotImplementedError

上面代码的重点是__subclasshook__类方法。__subclasshook__是抽象类的一个特殊方法&#xff0c;当你使用isinstance检查对象是否属于某个抽象类时&#xff0c;如果后者定义了这个方法&#xff0c;那么该方法就会被触发&#xff0c;然后&#xff1a;


  • 实例所属类型会作为参数传入该方法&#xff08;上面代码中的C参数&#xff09;&#xff1b;
  • 如果方法返回了布尔值&#xff0c;该值表示实例类型是否属于抽象类的子类&#xff1b;
  • 如果方法返回NotImplemented&#xff0c;本次调用会被忽略&#xff0c;继续进行正常的子类判断逻辑。

这意味着&#xff0c;下面这个和Validator没有继承关系的类&#xff0c;也被视作Validator的子类&#xff1a;

class StringValidator:
def validate(self, value):
pass
print(isinstance(StringValidator(), Validator))
# 输出&#xff1a;True

通过__subclasshook__类方法&#xff0c;我们可以定制抽象类的子类判断逻辑。这种子类化形式只关心结构&#xff0c;不关心真实继承关系&#xff0c;所以常被称为“结构化子类”。
这也是之前的ThreeFactory类能通过Iterable类型校验的原因&#xff0c;因为Iterable抽象类对子类只有一个要求&#xff1a;实现了__iter__方法即可。







推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • 本文介绍了Java调用Windows下某些程序的方法,包括调用可执行程序和批处理命令。针对Java不支持直接调用批处理文件的问题,提供了一种将批处理文件转换为可执行文件的解决方案。介绍了使用Quick Batch File Compiler将批处理脚本编译为EXE文件,并通过Java调用可执行文件的方法。详细介绍了编译和反编译的步骤,以及调用方法的示例代码。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 标题: ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • Java 11相对于Java 8,OptaPlanner性能提升有多大?
    本文通过基准测试比较了Java 11和Java 8对OptaPlanner的性能提升。测试结果表明,在相同的硬件环境下,Java 11相对于Java 8在垃圾回收方面表现更好,从而提升了OptaPlanner的性能。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • Java如何导入和导出Excel文件的方法和步骤详解
    本文详细介绍了在SpringBoot中使用Java导入和导出Excel文件的方法和步骤,包括添加操作Excel的依赖、自定义注解等。文章还提供了示例代码,并将代码上传至GitHub供访问。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 在开发中,有时候一个业务上要求的原子操作不仅仅包括数据库,还可能涉及外部接口或者消息队列。此时,传统的数据库事务无法满足需求。本文介绍了Java中如何利用java.lang.Runtime.addShutdownHook方法来保证业务线程的完整性。通过添加钩子,在程序退出时触发钩子,可以执行一些操作,如循环检查某个线程的状态,直到业务线程正常退出,再结束钩子程序。例子程序展示了如何利用钩子来保证业务线程的完整性。 ... [详细]
  • 花瓣|目标值_Compose 动画边学边做夏日彩虹
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Compose动画边学边做-夏日彩虹相关的知识,希望对你有一定的参考价值。引言Comp ... [详细]
author-avatar
欢颜是胖妞妞08
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有