python面向对象之属性和方法及其权限篇
学过C++或者Java里面,对于属性和方法的私有化是很了解,这里的权限也差不多比如私有化,就是只有你可以访问的属性,所以这里也差不多,不过不同的是,python其实并没有真正的实现私有化支持,但是,可以使用下划线完成伪私有的效果,类属性(方法)和实例属性(方法)遵循相同的规则
- 不加下划线的属性,表示为共有属性,例如:age
- 加一个下划线的属性,表示为受保护的属性,例如_name
- 加两个下划线的属性,表示为私有属性,例如__grade 方法类似
公有属性
在介绍公有属性之前,我们首先先来明确一个区域的概念,首先是我们一开始的类,即Person类,然后加上一个继承类Student,最后加上该python文件中的其他区域,最后是其他python文件,我们来看看,公有属性、私有属性和被保护的属性都可以被哪些区域访问到呢?首先来看看私有属性,示例代码如下:
class Person:
age = 18
def test(self):
print(Person.age)
print(self.age)
pass
pass
class Student(Person):
def test2(self):
print(Student.age)
print(self.age)
pass
pass
LS = Person()
LS.test()
ZS = Student()
ZS.test2()
print(Person.age)
print(LS.age)
print(Student.age)
print(ZS.age)
# 显然调用没有问题 类和对象 和 子类及子类对象都可以访问,显然在同一python文件和其他python文件中也可以访问
# 导入当时
import
from import *
受保护属性
class Person:
_age = 18
def test(self):
print(Person._age)
print(self._age)
pass
pass
class Student(Person):
def test2(self):
print(Student._age)
print(self._age)
pass
pass
LS = Person()
LS.test()
ZS = Student()
ZS.test2()
print(Person._age)
print(LS.age)
print(Student._age)
print(ZS._age)
# 父类及父类的实例可以访问到受保护的属性,子类及子类的实例也都可以访问
# 同时在同一python文件内也可以访问,但python会报警告,python不推荐这么做!
# 在其他python文件中,第一个导入方式(import ...)情况下受保护的属性也可以访问该python文件中其他区域受保护的属性,但依然会有警告
# 在其他python文件中,第二个导入方式(from ... import ...)情况下受保护的属性也可以访问该python文件中其他区域受保护的属性,但依然会有警告
# 但加上下面这句以后就可以被第二个导入方式进行访问
__all__ = ["_a"]
私有属性
class Person:
__age = 18
def test(self):
print(Person.__age)
print(self.__age)
pass
pass
class Student(Person):
def test2(self):
print(Student.__age)
print(self.__age)
pass
pass
LS = Person()
LS.test()
ZS = Student()
ZS.test2()
print(Person.__age)
print(LS.__age)
print(Student.__age)
print(ZS.__age)
# 显然可以被父类及其父类的实例访问到
# 但显然不可以被子类及其子类的实例进行访问
# 在本python文件中其他区域范围也不能进行访问
# 在外python文件中,与受保护的类型一致
私有属性的名字重整机制
之前提到过python的私有属性是一种伪私有属性,那么它是怎么实现的呢?主要靠的是一个名字重整机制,比如重改__x为另外一个名称。目的是:
- 防止外界直接访问
- 防止被子类同名称属性覆盖
class Person:
__age = 18
def test(self):
print(Person.__age)
print(self.__age)
pass
pass
LS = Person()
print(Person._Animal__X)
私有属性的应用场景
- 数据保护
- 数据过滤
class Person:
def __init__(self):
self.__age = None
pass
def setAge(self,age):
# 数据过滤
if isinstance(value,int) and 0 < age < 150
self.__age = age
def getAge(self):
return self.__age
pass
# 使用__init__的作用在于当我们创建好一个 实例对象以后,会自动调用这个方法,来初始化这个对象
# 这里需要注意的是,你在外面直接调用私有属性,会自动添加一个新的属性,例如
ZS = Student()
ZS.__age = 21 # 会重新创建一个私有属性为__age
print(ZS.__age) # 这里输出21
print(ZS.getAge()) # 这里输出18
变量添加下划线的规划
- 规范:与系统进行区分,系统自带的函数一般为:
# 系统自带
X__ __X__
# 自己写法
X_ __x_
只读属性
只读属性(一般指实例属性),只能读取,不能写入。
- 应用场景:直线内部根据不同场景进行修改,而对外界来说,不能修改,只能读取
- 方式一:
# 方案:私有化,既不能读也不能写 然后加一个可以读的函数
class Person(object):
def __init__(self):
self.__age = None
pass
# 主要作用就是,可以以使用属性的方式,来使用这个方法
@property
def getAge(self):
return self.__age
pass
ZS = Student()
print(ZS.getAge()) # 这里输出18
# 这里存在一个问题,如果一个不知道,可以自己来给age赋值,这样它会误以为改变,其实只是增加了一个新的属性
# 所以需要加一个property 让它直接可以直接输出
print(ZS.getAge) # 这里输出18
# 如果赋值就会报错
ZS.getAge = 21
- 方式二
# 上面的方式虽然能够只读,但如果通过底层去修改,方式二也不能避免,__dict__也是可以修改的
class Person(object):
# 当我们通过实例,属性 = 值,给一个实例增加一个属性,或者说,修改一下属性值的时候,都会调用这个方法
# 在这个方法内部,才会真正的把,这个属性,以及对应的数据,给存储到__dict__字典里面
def __setattr__(self,key,value):
# 找到私有属性的age,并且age已经在对象中了
if key == 'age' and key in self.__dict__.keys():
# 什么都不做
else:
self.__dict__[key] = value
# 不能使用self.key = value,这样会陷入无限调用此方法
pass
pass
# 这里只限制age,其他属性可以正常修改
property的作用
- 作用:将”一些属性的操作方法”关联到某一个属性中 该函数含有三个操作 :得到 设置 删除
# 注意版本,property只有在新式类才能使用,所以为了兼容,建议直接直接继承Object 第一种方式如下:
class Person(object):
def __init__(self):
self.__age = None
pass
# 主要作用就是,可以以使用属性的方式,来使用这个方法
@property
def getAge(self):
return self.__age
def __setAge(self,value):
self.age = value
pass
age = property(getAge,setAge)
pass
ZS = Student()
# 均不报错
print(ZS.age)
ZS.age = 21
# 第二种方式:
class Person(object):
def __init__(self):
self.__age = None
pass
# 主要作用就是,可以以使用属性的方式,来使用这个方法
@property
def age(self):
return self.__age
@age.setter
def __age(self,value):
self.age = value
pass
pass
# 在经典类中的使用:
# _*_ encoding:utf-8 _*_
class Person(object):
def __init__(self):
self.__age = None
pass
# 主要作用就是,可以以使用属性的方式,来使用这个方法
@property
def getAge(self):
return self.__age
def __setAge(self,value):
self.age = value
pass
age = property(getAge,setAge)
pass
ZS = Student()
ZS = Student()
# 均不报错
print(ZS.age)
# 但是注意经典类只能读取,写的时候写不了,指挥新增一个类
ZS.age = 21
经典类与新式类的概念
- 经典类:没有继承了(object)
- 新式类:继承了(object) python3.x 定义一个类时,默认继承(object) ,建议使用新式类
# 可以通过__bases__查看积累,查看当前的类是不是新式类
Student.__bases__
常用内置属性
# 类的内置属性有:
__dict__:类的属性
__bases__:类的所有父类构成的元组 # 说明python支持多继承
__doc__:类的文档字符串
__name__:类名
__module__:类定义所在的模块
# 实例属性:
__dict__:实例的属性
__class__:实例对应的类
私有方法
与私有属性非常相似
class Person(object):
def __study(self): # 与之前属性的名字重整机制相似 注意不要命名把原来方法进行覆盖
print("I am learning !")
内置特殊方法
# 使用意义:实现不同的特定操作
# 分类:
# 1. 生命周期方法
# 2. 其他内置方法: 信息格式化操作 调用操作 索引操作 切片操作 迭代器 描述器
# 信息格式化操作
__str__ # 在类中实现该方法,在print(对象实例) 的时候会执行该函数
__repr__
# 当实现上面的方法,需要查看该实例的对象时,可以使用内置的repr的时候,打印对象实例时,都会先找str再找repr方法 直接交互的情况下,repr优先级比str优先级高
# repr更多面向开发人员
# 调用操作
__call__ # 使得”对象“具备当做函数,来调用的能力
# 实现了__call__ 这个函数以后,就可以时用ZhangSan() 参数: 前:元组 后:字典
# 应用参数:类似于偏函数,,偏向某一方面
# 偏函数的使用:
def createPen(color,type):
print("创建了一个%s这个类型的画笔,它是%s颜色" %(type,color))
pass
import functools
gangbiFunc = functools.partial(createPen,type="钢笔")
gangbiFunc(color="黑色")
# 这个时候只需要修改黑色就可以完成了多种颜色的钢笔
# 用__call__实现
class PenFactory:
def __init__(self,type):
self.type = type
pass
def __call__(self,color):
print(创建了一个%s这个类型的画笔,它是%s颜色" %(type,color))
pass
gangbi = PenFactory("钢笔")
gangbi("黑色")
# 索引操作:可以让一个实例对象进行索引操作
class Person:
def __setitem__(self,key,value):
print("set",key,value)
self.cache[key] = value
pass
def __getitem__(self,item):
print("getitem",item)
return self.cache[item]
pass
def __delitem__(self,key):
print("delitem",key)
del self.cache[key]
pass
pass
p = Person()
p["name"] = "SZ" # 会调用setitem
p["name"] # 会调用getitem
del p["name"] # 会调用delitem
下面进行切片操作:
l = [1,2,3,4,5]
print(l[1: 4: 2]) #开始 结尾 步长 输出 2 4
# 使得类也可以进行切片 分为了两种,一种python2.0的 一种是python3.0的
# 在python2.0当中,可以直接实现三个内置方法 __setspice__ __get... __del...
# 然后可以直接按照切片的方式操作对象 p[1,6,2]
# 注意过期
# 在python 3.x当中,统一由”索引操作“进行管理 def __setitem__(self,key,value) 其他如上
class Person:
self.items = [1,21,3,4,5,6,7,8]
def __setitem__(self,key,value):
print("set",key,value)
self.cache[key] = value
if isinstance(key,slice):
self.items[key.start: key.stop: key.step] = value
pass
pass
p =Person()
p[0: 4: 2]= [1, 2] # 这里key是一个slice对象,它的三个属性是开始 结束 步长 这里是替换掉第0个和第2个开始
下面进行比较操作:
class a:
# ==
def __eq__(self,other):
print(other)
pass
# True 还是 False 需要return 判别式,例如 retrun self.age == other.age
#
def __
pass
a1 = A()
a2 = A()
print (a1 > a2) # 在python3.0不能直接比较
# 有点像C++里面的运算符重载
# != : __ne__ 不等于不写,会自动根据等于操作进行运算
# > : __gt__
# < : __lt__
# >= : __ge__
# <= : __le__
# 如果对于反向操作的比较运算符,只定义了其中一个方法,但使用的是另外一种比较运算符,那么,解释器会采用调换参数的方式进行调用该方法,但不知叠加操作(意思小于操作 + 等于操作 不等于 小于等于操作)
# 但其实有办法可以自动补全,方法是采用函数模块的自动补全注解,如下
import functools
@functools.total_ordering
class a:
... # 这里只需要你写了lt eq ,会给你自动补齐其他几个相反的操作,甚至会有小于等于 但一旦返回了True 或者 False 那么就不会执行另外一个操作
pass
# 上下文的布尔值,就是把对象实例当作一个bool值进行判定
class A:
def __bool__(self):
return True
a = A()
if a:
print("aaa")
下面进行便利操作:
# 怎么样让我们自己创建的对象可以使用for in进行便利?
# 怎么样让我们自己创建的对象可以使用next函数进行访问?(迭代器)
# 首先是for in 两种方式
# 方式一如下:
class Person:
self.age = 0
def __getitem__(self, item):
self.result += 1
if self.result >= 18:
raise StopIteration("停止遍历") # 抛出异常
return self.result
pass
p = Person()
for i in p:
print(i)
pass
# 方式2:
class Person:
self.age = 0
def __getitem__(self, item):
self.result += 1
if self.result >= 18:
raise StopIteration("停止遍历") # 抛出异常
return self.result
def __iter__(self): # 优先级比上面高 效果取得一个迭代器,不断调用next方法
print("iter")
return self
def __next__(self): #其实去掉iter函数,也会自动迭代一个程序,但加上,会自动全部遍历,指导抛出异常
self.result += 1
if self.result >= 18:
raise StopIteration("停止遍历") # 抛出异常
return self.result
pass
p = Person()
for i in p:
print(i)
pass
# 恢复迭代器初始值
# 只需要在iter把该值回归为0即可 先进iter 再进next 所以必须实现这两个函数才能是一个迭代器,iter是将对象转换成为一个迭代器使用 这里存疑 集数73
以下是描述器相关知识:
# 对属性操作添砖加瓦,有点像对属性做一个代理模式
class Person:
def __init__(self):
self.__age = 10
pass
def get_age(self):
return self.__age
def set_age(self,value):
if value < 0:
value = 0
self.__age = value
pass
pass
p = Person()
# 通过property进行实现
age = property(get_age,set_age)
p.age # 即可 详情看上面的方法
# 第二种方式,其实就是代理模式,不错的思路就是,你把一个对象(该对象就是age 里面有这个age 的set get方法)当成另一个类的对象去进行处理即可,这样就可以很好的进行操作
# 但这种方式在经典类中并不是直接转接到age类里面,在新式类(都继承object)里面反而就可以
# 调用细节:一个实例属性的正常访问顺序:
# 实例对象自身的dict字典中查找
# 对应类对象中的dict字典中查找
# 如果有父类,会再往上层的dict字典中检测
# 如果没有找到,但定义了getattr方法,会调用这个方法
# 而在上述整个过程中,是如何将描述其的get方法给嵌入到查找机制当中的呢
# 通过getattribute方法实现,如果实现了描述其方法get就会直接调用,如果没有,则按照上面的机制去查找,一旦覆盖就不会调用get方法
# 描述器被分为了资料描述器(实现了get set 方法) 非资料描述器(实现了get方法)
# 优先级: 资料描述器 > 实例属性 > 非资料描述器
# 描述器怎么存的呢?
# 其实是直接存到Age类对象里面去了,所以age属性会被Person对象共有,这个时候,所以在Age对象中必须选择instance对象来进行存储
class Age:
...
def __set__(self,instance,owner,value): # instance表示的是Person的对象实例
instance.v = value
pass
...
pass
class Person:
age = Age()
pass
下面介绍装饰器
# 在保证原有的函数不改变的情况,给函数增加一定的功能
def check(func):
def inner():
print("check !")
func() #下面的learn函数
return inner
pass
@check
def learn():
print("I am learning")
pass
# 这里的亮点就是可以将一个函数传上去,因此我们可以在类里面通过call来实现先调用learn来实现其的执行顺序