PythonI/O进阶学习笔记_3.2面向对象编程_python的封装

 

前言:

本篇相关内容分为3篇多态、继承、封装,这篇为第三篇 封装。

本篇内容围绕 python基础教程这段:
在面向对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法。使用对象而非全局变量和函数的原因有多个,下面列出了使用对象的最重要的好处。
 多态:可对不同类型的对象执行相同的操作,而这些操作就像“被施了魔法”一样能够正常运行。
 封装:对外部隐藏有关对象工作原理的细节。
 继承:可基于通用类创建出专用类。
内容较多,这篇为下篇。

Content:

- 封装

1.数据封装和私有属性

2. 类变量和实例变量(对象变量)

3. 类属性和实例属性得查找顺序(MRO)

4. 静态方法 类方法和对象方法使用以及参数

5. python的接口和自省机制

6. 上下文管理器

====================================

1.python的数据封装和私有属性

a.python的__私有属性

python用__开头完成私有属性的封装。用__开头的属性名或者方法就没法直接外部获取,只有类中的公共方法才可以访问。

python的数据封装和java c++这种静态语言不同的是,静态语言其实本身有private类型的。而python是用小技巧实现了这种私有属性。

 
b.如何实现的?
以__开头的变量,python会对它进行变形,变形的模式是加上当前的class和变形的名称。
例: 如下的User中的__birthday变量,最后调用的时候变为_User__birthday
实际上在python中这并不是真正的安全。其实即使在java里的private关键词对反射机制比较了解其实也不是绝对安全性。
python比java突破这个安全性更加简单。
 
c.一般什么情况用这种私有属性?
  • 隐藏起一个属性,不想让外部调用
  • 保护这个属性,不想让这个属性随意改变
  • 保护这个属性不被子类继承

 

2.类变量和实例变量

a.什么是python的类变量?

类下的变量。

例:
class A():
    aa=1
其中aa就是类变量。
 
b.什么是实例变量?
class A:
    aa=1
    def __init__(self,x,y):
        self.x=x
        self.y=y
其中,self是传递的对象(实例本身),x,y是对象参数。
先查找对象变量,再查找类变量。
 
c.类变量和实例变量的区别
类A是没有对象x和y的。 但是实例化的a有。
 
d.类变量被修改的影响 和实例变量被修改的影响
 
如果修改实例对象中aa的值,修改结果如何改变呢?
修改对象变量的类变量的值的时候,相当于多新建了一个对象a中的aa的值,并且实例a的对象值得查找顺序是:
先查找自己实例中,是否有这个变量,再往上查找。
 
3. 类属性和实例属性得查找顺序(MRO)
在继承那篇中,就有讲到MRO查找顺序,但是重点是在于查找子类的父类继承顺序,那么同理得到的属性查找顺序是什么样的呢?
回顾一下C3算法,这篇讲的比较明白:https://blog.csdn.net/u011467553/article/details/81437780
咱们再来看下面的输入,是否能看出输出是什么呢?
class Init(object):
    def __init__(self, v):
        #print("init")
        self.val = v
        #print("init:",self.val)
class Add2(Init):
    def __init__(self, val):
        #print("Add2")
        super(Add2, self).__init__(val)
        #print("add2:",self.val)
        self.val += 2
class Mult(Init):
    def __init__(self, val):
        #print("Mult")
        super(Mult, self).__init__(val)
        #print("Mult:",self.val)
        self.val *= 5
class HaHa(Init):
    def __init__(self, val):
        #print("HAHA")
        super(HaHa, self).__init__(val)
        #print("Haha:",self.val)
        self.val /= 5
class Pro(Add2,Mult,HaHa): #
    pass
class Incr(Pro):
    def __init__(self, val):
        super(Incr, self).__init__(val)
        self.val+= 1
# Incr Pro Add2 Mult HaHa Init
p = Incr(5)
print(p.val)
c = Add2(2)
print(c.val)

 把代码中的print注释都拿掉,可以发现整个流程为:

 

4. 静态方法 类方法和对象方法使用以及参数

a.静态方法 staticmethod

- 静态方法定义:

   使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法

- 静态方法的一些特点:

   静态方法是类中的函数,不需要实例。

   静态方法主要是用来存放逻辑性的代码,主要是一些逻辑属于类,但是和类本身没有交互,即在静态方法中,不会涉及到类中的方法和属性的操作。

   可以理解为将静态方法存在此类的名称空间中。事实上,在python引入静态方法之前,通常是在全局名称空间中创建函数。- 

- 静态方法调用:

例:我要在类中实现一个得到现在时间的方法。与传进去的任何参数都无关那种。

import time


class TimeTest(object):
    def __init__(self, hour, minute, second):
        self.hour = hour
        self.minute = minute
        self.second = second

    @staticmethod
    def showTime():
        return time.strftime("%H:%M:%S", time.localtime())


print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime = t.showTime()
print(nowTime)

 

b.类方法

- 类方法定义:

    使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法)

- 类方法的一些特点:

    将类本身作为对象进行操作。

    不管这个方式是从实例调用还是从类调用,它都用第一个参数把类传递过来

- 类方法使用:

例:实现一个基类做一个有颜色属性的抽象共性,对于实际的颜色的值需要结合实际子类传递的值进行匹配

class ColorTest(object):
    color = "color"

    @classmethod
    def value(self):
        return self.color
class Red(ColorTest):
    color = "red"
class Green(ColorTest):
    color = "green"
g = Green()
print(g.value())
print(Green.value())

这时候可能有人会觉得,这个跟实例方法(普通方法)不是一样的嘛,继承父类,并且用继承中的知识重写父类中的属性或者方法?

重点就在于,如果我们把@classmethod这个方法去掉,Green.value()这样去调用是会报错的。

 

因为需要传递实例参数进去,而不能直接用类调用。

 

c.对象方法(实例方法)

- 实例方法定义:

    第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法)

- 实例方法的一些特点:

    只能由类的实例来调用,就是我们平时最常用的。

比较简单,没有特殊装饰器,暂不举例。

 

5.python的接口和自省机制

a.什么是python的自省机制

当我们需要实现一个通用的DBM框架时,可能需要对数据对象的字段赋值,但我们无法预知用到这个框架的数据对象都有些什么字段,换言之,我们在写框架的时候需要通过某种机制访问未知的属性。

也就是说,我们需要在很多时候去访问python自己为我们做了哪些隐藏的事情,或者某个框架里具体实现了哪些方法等。

通过python的自省机制去让python告诉我们,我们查询的对象是什么,有哪些功能等。

 

b.python自省之访问对象的属性

例有下面这个类,并且实例化了一个a对象。

class Company(object):
    def __init__(self,company_name,staffs=[]):
        self.company_name=company_name
        self.staffs=staffs
    def add(self,staff):
        self.staffs.append(staff)
    def remove(self,staff):
        self.staffs.remove(staff)

user_list=['tangrong1','tangrong2','tangrong3']
a=Company("aaa",user_list)

我需要去得到类里的一些方法和属性:

####访问对象的属性
#dir() 调用这个方法将返回包含obj大多数属性名的列表(会有一些特殊的属性不包含在内)。obj的默认值是当前的模块对象。
print("dir()")
print(dir(Company))
print(dir(a))
#hasattr(obj,attr) 这个方法用于检查obj是否有一个名为attr的值的属性,返回一个布尔值
print("hasattr()")
print(hasattr(a,"add"))
print(hasattr(a,"staffs"))
print(hasattr(a,"a"))
#getattr(obj, attr) 调用这个方法将返回obj中名为attr值的属性的值,例如如果attr为'staffs',则返回obj.staffs。
print("getattr()")
print(getattr(a,"add"))
print(getattr(a,"staffs"))
#setattr(obj, attr, val) 调用这个方法将给obj的名为attr的值的属性赋值为val。例如如果attr为'bar',则相当于obj.bar = val。
print("setattr()")
print(setattr(a,"staffs",["tangrong4","tangrong5"]))
print(a.staffs)

输出为:

dir() ##ps:可以发现,实例和类的dir()列出来的有些不一样
['__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__',
'add', 'remove'] ['__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__',
'add', 'company_name', 'remove', 'staffs']
hasattr() True True False
getattr()
<bound method Company.add of <__main__.Company object at 0x000002D3172F2518>> ['tangrong1', 'tangrong2', 'tangrong3']
setattr() None [
'tangrong4', 'tangrong5']

 

c.python自省之访问对象的元数据

包括各种__dict__()\__doc__\__bases__等

内容比较多,可看这篇:https://www.cnblogs.com/huxi/archive/2011/01/02/1924317.html

 

6.上下文管理器

a.什么是上下文管理器?

上下文管理器就是实现了上下文管理协议的对象。主要用于保存和恢复各种全局状态,关闭文件等,上下文管理器本身就是一种装饰器。

感觉上句很像白说是不是- -。实际例就是,类似于with语句,就是遵循了上下文管理协议,才能在内部我们看不到的地方,帮我们完成了退出时做出关闭文件、执行自定义代码块的操作的。就不用我们显示判断调用读取完了就关闭文件这种操作。

with open("test/test.txt","w") as f_obj:
  f_obj.write("hello")

b.用with看他遵循的上下文管理协议

上下文管理协议包括两个方法:

  • contextmanager.__enter__() 从该方法进入运行时上下文,并返回当前对象或者与运行时上下文相关的其他对象。如果with语句有as关键词存在,返回值会绑定在as后的变量上。

  • contextmanager.__exit__(exc_type, exc_val, exc_tb) 退出运行时上下文,并返回一个布尔值标示是否有需要处理的异常。如果在执行with语句体时发生异常,那退出时参数会包括异常类型、异常值、异常追踪信息,否则,3个参数都是None。

能用with语句的对象,也是因为这个对象里,遵循了这个协议。

with语句就是为支持上下文管理器而存在的,使用上下文管理协议的方法包裹一个代码块(with语句体)的执行,并为try...except...finally提供了一个方便使用的封装。

我们创建一个能支持with(上下文管理协议)的类,这个类实现了db最开始建立连接,退出时关闭连接的操作。

import sqlite3
 
class DataConn:
  def __init__(self,db_name):
    self.db_name = db_name
 
  def __enter__(self):
    self.conn = sqlite3.connect(self.db_name)
    return self.conn
 
  def __exit__(self,exc_type,exc_val,exc_tb):
    self.conn.close()
    if exc_val:
      raise
 
if __name__ == "__main__":
  db = "test/test.db"
  with DataConn(db) as conn:
    cursor = conn.cursor()

 

c.用contextlib自定义上下文管理器

from contextlib import contextmanager
 
@contextmanager
def file_open(path):
  try:
    f_obj = open(path,"w")
    yield f_obj
  except OSError:
    print("We had an error!")
  finally:
    print("Closing file")
    f_obj.close()
 
if __name__ == "__main__":
  with file_open("test/test.txt") as fobj:
    fobj.write("Testing context managers")

或者简单版:

 

这个装饰器装饰的一定要是生成器。
__enter__中的代码都是yield之前的逻辑代码。
__exit__代码实在yield之后实现的代码逻辑。

 

posted @ 2019-09-10 17:24  besttr1225  阅读(429)  评论(1编辑  收藏  举报