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来实现其的执行顺序