Python学习——02-Python基础——8-面向对象的程序设计——封装绑定方法与非绑定方
Posted c富-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python学习——02-Python基础——8-面向对象的程序设计——封装绑定方法与非绑定方相关的知识,希望对你有一定的参考价值。
十 封装
1引子
从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装=‘隐藏’,这种理解是相当片面的
2先看如何隐藏
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
#其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形 #类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式: class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print(\'from A\') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. #A._A__N是可以访问到的,#这种,在外部是无法通过__x这个名字访问到。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。
2.变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况 >>> class A: ... def fa(self): ... print(\'from A\') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print(\'from B\') ... >>> b=B() >>> b.test() from B #把fa定义成私有的,即__fa >>> class A: ... def __fa(self): #在定义时就变形为_A__fa ... print(\'from A\') ... def test(self): ... self.__fa() #只会与自己所在的类为准,即调用_A__fa ... >>> class B(A): ... def __fa(self): ... print(\'from B\') ... >>> b=B() >>> b.test() from A
3封装不是单纯意义的隐藏
1:封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class Teacher: def __init__(self,name,age): # self.__name=name # self.__age=age self.set_info(name,age) def tell_info(self): print(\'姓名:%s,年龄:%s\' %(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): raise TypeError(\'姓名必须是字符串类型\') if not isinstance(age,int): raise TypeError(\'年龄必须是整型\') self.__name=name self.__age=age t=Teacher(\'egon\',18) t.tell_info() t.set_info(\'egon\',19) t.tell_info()
2:封装方法:目的是隔离复杂度
封装方法举例:
1. 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
2. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
3. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 #隔离了复杂度,同时也提升了安全性 class ATM: def __card(self): print(\'插卡\') def __auth(self): print(\'用户认证\') def __input(self): print(\'输入取款金额\') def __print_bill(self): print(\'打印账单\') def __take_money(self): print(\'取款\') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw() 隔离复杂度的例子
3: 了解
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点
python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象进阶
4 特性(property)
什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight / (self.height**2) p1=People(\'egon\',75,1.85) print(p1.bmi)
例二:圆的周长和面积
import math class Circle: def __init__(self,radius): #圆的半径radius self.radius=radius @property def area(self): return math.pi * self.radius**2 #计算面积 @property def perimeter(self): return 2*math.pi*self.radius #计算周长 c=Circle(10) print(c.radius) print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 print(c.perimeter) #同上 \'\'\' 输出结果: 314.1592653589793 62.83185307179586 \'\'\'
#注意:此时的特性arear和perimeter不能被赋值 c.area=3 #为特性area赋值 \'\'\' 抛出异常: AttributeError: can\'t set attribute \'\'\'
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
除此之外,看下
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo: def __init__(self,val): self.__NAME=val #将所有的数据属性都隐藏起来 @property def name(self): return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TypeError(\'%s must be str\' %value) self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise TypeError(\'Can not delete\') f=Foo(\'egon\') print(f.name) # f.name=10 #抛出异常\'TypeError: 10 must be str\' del f.name #抛出异常\'TypeError: Can not delete\'
class Foo: def __init__(self,val): self.__NAME=val #将所有的数据属性都隐藏起来 def getname(self): return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) def setname(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TypeError(\'%s must be str\' %value) self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME def delname(self): raise TypeError(\'Can not delete\') name=property(getname,setname,delname) #不如装饰器的方式清晰 了解:一种property的古老用法
5封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 return self.__width * self.__length #使用者 >>> r1=Room(\'卧室\',\'egon\',20,20,20) >>> r1.tell_area() #使用者调用接口tell_area #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 return self.__width * self.__length * self.__high #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 >>> r1.tell_area()
十一 绑定方法与非绑定方法
1类中定义的函数分成两大类
一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
1. 绑定到类的方法:用classmethod装饰器装饰的方法。
为类量身定制
类.boud_method(),自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
2. 绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
二:非绑定方法:用staticmethod装饰器装饰的方法
1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
2 绑定方法
绑定给对象的方法(略)
绑定给类的方法(classmethod)
classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法
HOST=\'127.0.0.1\' PORT=3306 DB_PATH=r\'C:\\Users\\Administrator\\PycharmProjects\\test\\面向对象编程\\test1\\db\'
mport settings class MySQL: def __init__(self,host,port): self.host=host self.port=port @classmethod def from_conf(cls): print(cls) return cls(settings.HOST,settings.PORT) print(MySQL.from_conf) #<bound method MySQL.from_conf of <class \'__main__.MySQL\'>> conn=MySQL.from_conf() conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类
3非绑定方法
在类内部用staticmethod装饰的函数即非绑定方法,就是普通函数
statimethod不与类或对象绑定,谁都可以调用,没有自动传值效果
import hashlib import time class MySQL: def __init__(self,host,port): self.id=self.create_id() self.host=host self.port=port @staticmethod def create_id(): #就是一个普通工具 m=hashlib.md5(str(time.time()).encode(\'utf-8\')) return m.hexdigest() print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数 conn=MySQL(\'127.0.0.1\',3306) print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数
4 classmethod与staticmethod的区别
import settings class MySQL: def __init__(self,host,port): self.host=host self.port=port @staticmethod def from_conf(): return MySQL(settings.HOST,settings.PORT) # @classmethod #哪个类来调用,就将哪个类当做第一个参数传入 # def from_conf(cls): # return cls(settings.HOST,settings.PORT) def __str__(self): return \'就不告诉你\' class Mariadb(MySQL): def __str__(self): return \'<%s:%s>\' %(self.host,self.port) m=Mariadb.from_conf() print(m) #我们的意图是想触发Mariadb.__str__,但是结果触发了MySQL.__str__的执行,打印就不告诉你: mariadb是mysql
5 练习
定义MySQL类
1.对象有id、host、port三个属性
2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一
3.提供两种实例化方式,方式一:用户传入host和port 方式二:从配置文件中读取host和port进行实例化
4.为对象定制方法,save和get_obj_by_id,save能自动将对象序列化到文件中,文件路径为配置文件中DB_PATH,文件名为id号,保存之前验证对象是否已经存在,若存在则抛出异常,;get_obj_by_id方法用来从文件中反序列化出对象
原文链接:http://www.cnblogs.com/dkblog/archive/2011/10/10/2205200.html Python官方Doc:《20.15. uuid — UUID objects according to RFC 4122》 UUID的算法介绍:《A Universally Unique IDentifier (UUID) URN Namespace》 概述: UUID是128位的全局唯一标识符,通常由32字节的字符串表示。 它可以保证时间和空间的唯一性,也称为GUID,全称为: UUID —— Universally Unique IDentifier Python 中叫 UUID GUID —— Globally Unique IDentifier C# 中叫 GUID 它通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生成ID的唯一性。 UUID主要有五个算法,也就是五种方法来实现: 1、uuid1()——基于时间戳 由MAC地址、当前时间戳、随机数生成。可以保证全球范围内的唯一性, 但MAC的使用同时带来安全性问题,局域网中可以使用IP来代替MAC。 2、uuid2()——基于分布式计算环境DCE(Python中没有这个函数) 算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。 实际中很少用到该方法。 3、uuid3()——基于名字的MD5散列值 通过计算名字和命名空间的MD5散列值得到,保证了同一命名空间中不同名字的唯一性, 和不同命名空间的唯一性,但同一命名空间的同一名字生成相同的uuid。 4、uuid4()——基于随机数 由伪随机数得到,有一定的重复概率,该概率可以计算出来。 5、uuid5()——基于名字的SHA-1散列值 算法与uuid3相同,不同的是使用 Secure Hash Algorithm 1 算法 使用方面: 首先,Python中没有基于DCE的,所以uuid2可以忽略; 其次,uuid4存在概率性重复,由无映射性,最好不用; 再次,若在Global的分布式计算环境下,最好用uuid1; 最后,若有名字的唯一性要求,最好用uuid3或uuid5。 编码方法: # -*- coding: utf-8 -*- import uuid name = "test_name" namespace = "test_namespace" print uuid.uuid1() # 带参的方法参见Python Doc print uuid.uuid3(namespace, name) print uuid.uuid4() print uuid.uuid5(namespace, name) 创建唯一id之UUID
#settings.py内容 \'\'\' HOST=\'127.0.0.1\' PORT=3306 DB_PATH=r\'E:\\CMS\\aaa\\db\' \'\'\' import settings import uuid import pickle import os class MySQL: def __init__(self,host,port): self.id=self.create_id() self.host=host self.port=port def save(self): if not self.is_exists: raise PermissionError(\'对象已存在\') file_path=r\'%s%s%s\' %(settings.DB_PATH,os.sep,self.id) pickle.dump(self,open(file_path,\'wb\')) @property def is_exists(self): tag=True files=os.listdir(settings.DB_PATH) for file in files: file_abspath=r\'%s%s%s\' %(settings.DB_PATH,os.sep,file) obj=pickle.load(open(file_abspath,\'rb\')) if self.host == obj.host and self.port == obj.port: tag=False break return tag @staticmethod def get_obj_by_id(id): file_abspath = r\'%s%s%s\' % (settings.DB_PATH, os.sep, id) return pickle.load(open(file_abspath,\'rb\')) @staticmethod def create_id(): return str(uuid.uuid1()) @classmethod def from_conf(cls): print(cls) return cls(settings.HOST,settings.PORT) # print(MySQL.from_conf) #<bound method MySQL.from_conf of <class \'__main__.MySQL\'>> conn=MySQL.from_conf() conn.save() conn1=MySQL(\'127.0.0.1\',3306) conn1.save() #抛出异常PermissionError: 对象已存在 obj=MySQL.get_obj_by_id(\'7e6c5ec0-7e9f-11e7-9acc-408d5c2f84ca\') print(obj.host)
其他练习
class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day @staticmethod def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间 t=time.localtime() #获取结构化的时间格式 return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回 @staticmethod def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间 t=time.localtime(time.time()+86400) return Date(t.tm_year,t.tm_mon,t.tm_mday) a=Date(\'1987\',11,27) #自己定义时间 b=Date.now() #采用当前时间 c=Date.tomorrow() #采用明天的时间 print(a.year,a.month,a.day) print(b.year,b.month,b.day) print(c.year,c.month,c.day) #分割线============================== import time class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day @staticmethod def now(): t=time.localtime() return Date(t.tm_year,t.tm_mon,t.tm_mday) class EuroDate(Date): def __str__(self): return \'year:%s month:%s day:%s\' %(self.year,self.month,self.day) e=EuroDate.now() print(e) #我们的意图是想触发EuroDate.__str__,但是结果为 \'\'\' 输出结果: <__main__.Date object at 0x1013f9d68> \'\'\' 因为e就是用Date类产生的,所以根本不会触发EuroDate.__str__,解决方法就是用classmethod import time class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day # @staticmethod # def now(): # t=time.localtime() # return Date(t.tm_year,t.tm_mon,t.tm_mday) @classmethod #改成类方法 def now(cls): t=time.localtime() return cls(t.tm_year,t.tm_mon,t.tm_mday) #哪个类来调用,即用哪个类cls来实例化 class EuroDate(Date): def __str__(self): return \'year:%s month:%s day:%s\' %(self.year,self.month,self.day) e=EuroDate.now() print(e) #我们的意图是想触发EuroDate.__str__,此时e就是由EuroDate产生的,所以会如我们所愿 \'\'\' 输出结果: year:2017 month:3 day:3 \'\'\'
十二 小白容易犯的错误
1.面向对象的程序设计看起来高大上,所以我在编程时就应该保证通篇class,这样写出的程序一定是好的程序(面向对象只适合那些可扩展性要求比较高的场景)
2.很多人喜欢说面向对象三大特性(这是从哪传出来的,封装,多态,继承?漏洞太多太多,好吧暂且称为三大特性),那么我在基于面向对象编程时,我一定要让我定义的类中完整的包含这三种特性,这样写肯定是好的程序
好家伙,我说降龙十八掌有十八掌,那么你
以上是关于Python学习——02-Python基础——8-面向对象的程序设计——封装绑定方法与非绑定方的主要内容,如果未能解决你的问题,请参考以下文章
Python学习——02-Python基础——4-变量与递归函数
Python学习——02-Python基础——5-迭代器协议和生成器
Python学习——02-Python基础——4-文件处理与三元运算
Python学习——02-Python基础——2-运算符与基本数据类型——课后作业