python 私有属性_Python入门类Class 高级篇

一、Python类中的访问限制(常用)

1、属性的访问限制,Python 私有属性

Python 类中如果有属性不希望被外部访问,我们可以在属性命名时以双下划线开头( __ ),那么该属性就不能使用原变量名访问,使得该属性变为本类私有的(伪私有)。

如果一个属性以"__xx__"的形式定义,那么它可以被外部访问。以"__xx__"定义的属性在 Python 的类中是特殊属性,有很多预定义的特殊属性都是以“__xxx__”定义,所以我们不要把普通属性用"__xxx__" 定义。

以单下划线开头的属性"_xxx",虽然也可以被外部访问,但,按照习惯,他们不应该被外部访问,遵照编码规范我们也不该在外部访问 _xx 或 __xx 属性。

补充说明:加双下划线__xx 的属性,可以通过“ _类名__xx ”可以访问到属性的值。

如下圆类Circle的示例,我们将pi属性开头加上双下划线变成私有属性:

class Circle(object):
__pi = 3.14

def __init__(self, r):
self.r = r

def area(self):
"""圆的面积"""
return self.r **2* self.__pi

circle1 = Circle(1)
print(Circle.__pi) # 抛出AttributeError异常
print(circle1.__pi) # 抛出AttributeError异常
通过 Circle.__pi 与 circle1.__pi 访问 __pi 时 都会出现 AttributeError 异常,证明访问权限已被控制。

那么是不是我们就不能访问 __pi 变量?

不是,其实还是可以访问 __pi ,可以通过 Circle._Circle__pi 访问到 __pi 属性,为什么能这么访问这里不多讲,他与python的机制有关。

按照编码规范,他们不应该使用Circle._Circle__pi 访问到__pi属性。

2、方法的访问限制,Python私有访问

同属性的访问限制,方法的访问限制也是在方法名前加双下划线 ( __ ),它也是一种伪私有。

示例:

class Circle(object):
__pi = 3.14

def __init__(self, r):
self.r = r

def area(self):
""" 圆的面积"""
return self.r**2 * self.__pi

def __girth(self):
"""圆的周长"""
return 2*self.r * self.__pi

circle1 = Circle(2)
print(circle1.__girth()) # 抛出AttributeError异常
私有化方法后,我们只能在类的内部使用该方法,不能被外部调用。

同属性控制,方法需要访问被限制的方法也是 _类名__xx 如 circle1._Circle__girth()。

二、Python类中的@classmethod、@staticmethod 装饰方法(比较难,需要理解)

@classmethod 用来修饰方法。使用在实例化前与类进行交互,但不和其实例进行交互的函数方法上。

@staticmethod 用来修饰类的静态方法。使用在有些与类相关函数,但不使用该类或该类的实例。如更改环境变量、修改其他类的属性等。

两者最明显的区别,classmethod 必须使用类的对象作为第一个参数,而staticmethod则可以不传递任何参数

1、@classmethod 类方法

类方法,我们不用通过实例化类就能访问的方法。而且@classmethod 装饰的方法不能使用实例属性,只能是类属性。它主要使用在和类进行交互,但不和其实例进行交互的函数方法上。

下面,我们要写一个只在类中运行而不在实例中运行的方法。

简单示例,读取私有化类属性数据,如下:

class Circle(object):
__pi = 3.14

def __init__(self, r):
self.r = r

@classmethod
def pi(cls):
return cls.__pi

def area(self):
"""圆的面积"""
return self.r ** 2 * self.__pi

print(Circle.pi()) # 没有实例化 能直接访问pi() 方法
circle1 = Circle(2)
print(circle1.pi()) # 也可以通过实例访问pi()方法
Circle类下的pi()方法被 @classmethod 装饰后,我们能通过Circle.pi() 直接运行方法,不用实例化类。

示例:重构构造__init__() 方法应用, 格式化创建时间实例

class Date(object):
day = 0
month = 0
year = 0

def __init__(self, year=0, month=0, day=0):
self.day = day
self.month = month
self.year = year

@classmethod
def from_string(cls, date_as_string):
year, month, day = date_as_string.split('-')
date = cls(year, month, day)
return date

date1 = Date.from_string('2017-10-17') # 直接使用固定格式的字符串就能创建Date的实例
print(date1.year, date1.month, date1.day)
from_string 返回的是Date类的实例,所以我们可以通过from_string 实例化类。 注意:from_string(cls, date_as_string)中cls表示的是类,它和self类实例有一定的差别。类方法中都是使用cls,实例方法中使用self。

2、@staticmethod 静态方法

@staticmethod 和@classmethod非常的相似,但是@staticmethod 不强制要求传递参数(它做的事与类方法或实例方法一样)。

@staticmethod使用在有些和类相关函数,但不使用该类或者该类的实例。如更改环境变量、修改其他类的属性等。

一句话@staticmethod 修饰的方法是放在类外的函数,我们为了方便将他移动到了类里面,它对类的运行无影响。

示例:

class Date(object):
day = 0
month = 0
year = 0

def __init__(self, year=0, month=0, day=0):
self.day = day
self.month = month
self.year = year

@classmethod
def from_string(cls, date_as_string):
year, month, day = date_as_string.split('-')
date = cls(year, month, day)
return date

@staticmethod
def is_date_valid(date_as_string):
"""用来校验日期的格式是否正确"""
year, month, day = date_as_string.split('-')
return int(year) <= 3999 and int(month) <= 12 and int(day) <= 31

date1 = Date.from_string('2012-05-10')
print(date1.year, date1.month, date1.day)
is_date = Date.is_date_valid('2012-09-18') # 格式正确 返回True

is_date_valid(date_as_string) 只有一个参数,它的运行不会影响类的属性,

注意:@staticmethod修饰方法 is_date_valid(date_as_string)中无实例化参数self或者cls;而@classmethod修饰的方法中有from_string(cls, date_as_string) 类参数cls。

三、Python 的 property 使用

property 的有两个作用:

  • 作为装饰器 @property 将类方法转换为类属性(只读)

  • property 重新实现一个属性的 setter 和 getter 方法

1、@property 将类方法转换为只读属性(常用)

使用 property 的最简单的方法是将它作为装饰器来使用。这可以让你将一个类方法转变成一个类属性。

示例:

class Circle(object):
__pi = 3.14

def __init__(self, r):
self.r = r

@property
def pi(self):
return self.__pi

circle1 = Circle(2)
print(circle1.pi)
circle1.pi = 3.14159 # 出现AttributeError异常
上面示例装饰了pi方法,创建实例后我们可以使用circle1.pi 自己获取方法的返回值,而且他只能读不能修改。

2、property 重新实现 setter 和 getter 方法(少用)

示例1:(用得较少)

class Circle(object):
__pi = 3.14

def __init__(self, r):
self.r = r

def get_pi(self):
return self.__pi

def set_pi(self, pi):
Circle.__pi = pi

pi = property(get_pi, set_pi)

circle1 = Circle(2)
circle1.pi = 3.14 # 设置 pi的值
print(circle1.pi) # 访问 pi的值
正如你所看到的,当我们以这种方式使用属性函数时,它允许pi属性设置并获取值本身而不破坏原有代码。让我们使用属性装饰器来重写这段代码,看看我们是否能得到一个允许设置的属性值。

示例2:(用得比示例1多)

class Circle(object):
__pi = 3.14

def __init__(self, r):
self.r = r

@property
def pi(self):
return self.__pi

@pi.setter
def pi(self, pi):
Circle.__pi = pi

circle1 = Circle(2)
circle1.pi = 3.14 # 设置 pi的值
print(circle1.pi) # 访问 pi的值

把一个方法变成属性,只需要加上 @property 就可以了,如上此时 pi(self) 方法,@property 本身又创建了另一个装饰器 @pi.setter,负责用 setter 方法给属性赋值,于是,将@pi.setter加到 pi(self, pi) 上,我们就拥有一个可控的属性操作。

四、Python类的继承

继承概念:继承是类与类的一种关系,是一种子类与父类的关系,即爸爸与儿子,爸爸生个儿子,儿子继承爸爸的属性和方法。

如猫类,猫是动物;猫类继承于动物类,动物类为父类也是所有动物的基类;猫类是动物类的子类,也是动物类的派生类。

Python有单继承与多继承。单继承即子类继承于一个类,多继承即子类继承于多个类,多继承会比较少遇到。

什么时候使用继承:假如我需要定义几个类,而类与类之间有一些公共的属性和方法,这时我就可以把相同的属性和方法作为基类的成员,而特殊的方法及属性则在本类中定义。这样子类只需要继承基类(父类),子类就可以访问到基类(父类)的属性和方法了,它提高了代码的可扩展性和重用行。

如下,定义一个动物类Animal为基类,它基本两个实例属性name和age、一个方法call。

class Animal(object):  #  python3中所有类都可以继承于object基类
def __init__(self, name, age):
self.name = name
self.age = age

def call(self):
print(self.name, '会叫')

# 现在我们需要定义一个Cat 猫类继承于Animal,猫类比动物类多一个sex属性。
class Cat(Animal):
def __init__(self,name,age,sex):
super(Cat, self).__init__(name,age) # 不要忘记从Animal类引入属性
self.sex=sex

if __name__ == '__main__': # 单模块被引用时下面代码不会受影响,用于调试
c = Cat('喵喵', 2, '男') # Cat继承了父类Animal的属性
c.call() # 输出 喵喵 会叫 ,Cat继承了父类Animal的方法
注意:一定要用 super(Cat, self).__init__(name,age) 去初始化父类,否则,继承自 Animal的 Cat子类将没有 name和age两个属性。
函数super(Cat, self)将返回当前类继承的父类,即 Animal,然后调用__init__()方法,注意self参数已在super()中传入,在__init__()中将隐式传递,不能再写出self。

二、Python 对子类方法的重构

上面例子中 Animal 的子类 Cat 继承了父类的属性和方法,但是我们猫类 Cat 有自己的叫声 '喵喵' ,这时我们可以对父类的 Call() 方法进行重构。如下:

class Cat(Animal):
def __init__(self, name, age, sex):
super(Cat, self).__init__(name,age)
self.sex = sex

def call(self):
print(self.name,'会“喵喵”叫')

if __name__ == '__main__':
c = Cat('喵喵', 2, '男')
c.call() # 输出:喵喵 会“喵喵”叫
类方法的调用顺序,当我们在子类中重构父类的方法后,Cat子类的实例先会在自己的类 Cat 中查找该方法,当找不到该方法时才会去父类 Animal 中查找对应的方法。

五、Python中子类与父类的关系

class Animal(object):
pass

class Cat(Animal):
pass

A= Animal()
C = Cat()

子类与父类的关系是 “is” 的关系,如上 Cat 继承于 Animal 类,我们可以说:

“A”是 Animal 类的实例,但“A”不是 Cat 类的实例。

“C”是 Animal 类的实例,“C”也是 Cat 类的实例。

判断对象之间的关系,我们可以通过 isinstance (变量,类型) 来进行判断:

print('"A" IS Animal?', isinstance(A, Animal))
print('"A" IS Cat?', isinstance(A, Cat))
print('"C" IS Animal?', isinstance(C, Animal))
print('"C" IS Cat?', isinstance(C, Cat))

输出结果:

"A" IS Animal? True
"A" IS Cat? False
"C" IS Animal? True
"C" IS Cat? True

拓展:isinstance() 判断变量类型

函数 isinstance() 不止可以用在我们自定义的类,也可以判断一个变量的类型,如判断数据类型是否为 int、str、list、dict 等。

print(isinstance(1000, int))  #输出 True
print(isinstance('1000', int)) # 输出 Flase

六、ython 中多态

类具有继承关系,并且子类类型可以向上转型看做父类类型,如果我们从 Animal 派生出 Cat和 Dog,并都写了一个 call() 方法,如下示例:

class Animal(object):
def __init__(self, name, age):
self.name = name
self.age = age
def call(self):
print(self.name, '会叫')

class Cat(Animal):
def __init__(self, name, age, sex):
super(Cat, self).__init__(name, age)
self.sex = sex
def call(self):
print(self.name, '会“喵喵”叫')

class Dog(Animal):
def __init__(self, name, age, sex):
super(Dog, self).__init__(name, age)
self.sex = sex
def call(self):
print(self.name, '会“汪汪”叫')

我们定义一个 do 函数,接收一个变量 ‘all’,如下:

def do(all):
all.call()

A = Animal('小黑',4)
C = Cat('喵喵', 2, '男')
D = Dog('旺财', 5, '女')

for x in (A,C,D):
do(x)

输出结果:

小黑 会叫
喵喵 会“喵喵”叫
旺财 会“汪汪”叫

小知识:多态

这种行为称为多态。也就是说,方法调用将作用在 all 的实际类型上。C 是 Cat 类型,它实际上拥有自己的 call() 方法以及从 Animal 继承的 call 方法,但调用 C .call() 总是先查找它自身的定义,如果没有定义,则顺着继承链向上查找,直到在某个父类中找到为止。

传递给函数 do(all) 的参数 all 不一定是 Animal 或 Animal 的子类型。任何数据类型的实例都可以,只要它有一个 call() 的方法即可。其他类不继承于 Animal,具备 call 方法也可以使用 do 函数。这就是动态语言,动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用。

七、Python类继承 注意事项:

在继承中基类的构造方法(__init__()方法)不会被自动调用,它需要在其派生类的构造方法中亲自专门调用。

在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。而在类中调用普通函数时并不需要带上self参数,Python 总是首先查找对应类的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)

58504d572e089441bd96096c8f132a37.png

下一期讲解
Python入门--模块导入import与from...import
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值