python面向对象

Posted xpc199151

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python面向对象相关的知识,希望对你有一定的参考价值。

编程语言的分类

编程语言的分类大体可分为三类,面向对象、面向过程、面向对象oop。

面向机器:它是抽象成机器指令,这样机器容易理解,代表为汇编语言。

面向过程:做一件事情,排出一个步骤,第一步做什么,第二步做什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理。它特点是问题规模小,可以步骤化,按部就班的处理。代表语言C语言

面向对象oop:随着计算机需要解决的问题的规模不断扩大,情况越来越复杂,需要很多人,很多部门协作,面向过程编程不太适合了。这个面对相对编程就出来了。代表语言:c++,Java,Python等

面向对象

面向对象适合大规模软件,是一种认识世界,分析世界的方法论,它将万事万物抽象为类。

类class

类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合。用计算机语言来描述类,就是属性和方法的集合。

对象instance、object

对事是类的具象,是一个实体,对于我们每个人这个个体,都是抽象概念人类的不同实体。

比如:你吃鱼,你就是对象,鱼也是对象,而吃是动作。你是具体的人,是具体的对象,你属于人类,人类是个抽象的概念,是无数具体的个体的抽象。鱼也是具体的对象,就是你吃的这一条具体的鱼,这条鱼属于鱼类,是无数的鱼抽象出来的概念。吃是动作,也是操作,也是方法,这个吃是你的动作,也就是人类具有的方法,如果反过来,鱼吃人,吃就是鱼类的动作了。

吃这个动作,很多动物都具有的动作,人类和鱼类都属于动物类,而动物类是抽象的概念,是动物都有吃的动作,但是吃法不同而已。

你驾驶车,这个车也是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的,是人类具有的方法。

属性:它是对象状态的抽象,用数据结构来描述。

操作:它是对象行为的抽象,用操作名和实现该操作的方法来描述。

每个人都有名字、身高、体重等信息,这些信息是个人的属性,但是这些信息不能保存在人类中,因为它是抽象的概念,不能保留具体的值。

而人类的实例,是具体的人,他可以存储这些具体的属性,而且可以不同人有不同的属性。

哲学:

一切皆对象

对象是数据和操作的封装

对事是独立的,但是对象之间可以相互作用。(人吃鱼)目前oop是最接近人类认知的编程范式。

面向对象3要素

封装

  1. 组装:将数据和操作封装到一起。
  2. 隐藏数据:对外只暴露一些借口,通过接口访问对象,比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解背后的机动原理。

继承

  1. 多复用,继承来的就不用自己写了。
  2. 多继承少修改,OCP(open-closed principle),使用继承来改变,来体现个性。

多态:面向对象编程最灵活的地方,动态绑定。(Python本身就是多态,它面向对象特别彻底

人类就是封装,人类继承自动物类,孩子继承父母特征,分为单一继承、多继承。多态:继承自动物类的人类、猫类的操作“吃”不同。

Python类

定义

class classname:
    语句块
  1. 必须使用class关键字。
  2. 类名必须是用大驼峰命名。
  3. 类定义完成后,就产生了一个类对象(类本身也是一个对象),绑定到了标识符ClassName上。
class MyClass:
    """A example class"""
    x = "abc"#类属性
    
    def foo(self):#类属性foo,也是方法
        return "My class"
    
print(MyClass.x)
print(MyClass.foo)
print(MyClass.__doc__)
print(type(MyClass)) 结果为: abc
<function MyClass.foo at 0x00000000059C2488> A example class
<class ‘type‘>
 

类对象及类属性

类对象:类的定义就会生成一个类对象。

类的属性:类定义中的变量和类中定义的方法都是类的属性。

类变量:上面的例子中的x就是MyClass的变量。

上面的例子中,x、fool都是类的属性,__doc__也是类的属性。

foo方法是类的属性,如同吃是人类的方法,但是每一个具体的人才能吃东西,也就是说吃是人的实例才能调用的方法。

foo是method方法,不是普通的函数对象function了,它一般要求有一个参数,第一个参数可以是self(self只是个惯用标识符,可以换名字),这个参数位置就留给了self。

self指代的是当前实例本身。

实例化

a = MyClass()#实例化,初始化
type(a)

结果为:
__main__.MyClass
class MyClass:
    """A example class"""
    x = "abc"#类属性
    
    def foo(self):#类属性foo,也是方法
        return self.x
    
print(MyClass)
print(MyClass.__name__)
print(MyClass.x)
print(MyClass.foo)
print(MyClass.__doc__)
print(type(MyClass))


print("```````````````````````")
a = MyClass()
print(a.foo())
print(a.x)
print(a.foo)


结果为:
<class __main__.MyClass>
MyClass
abc
<function MyClass.foo at 0x03B60618>
A example class
<class type>
```````````````````````
abc
abc
<bound method MyClass.foo of <__main__.MyClass object at 0x03B6F290>>
class MyClass:
    """A example class"""
    x = "abc"#类属性
    
    def foo(self):#类属性foo,也是方法
        print(id(self))
        return self
    
print(MyClass)
print(MyClass.__name__)
print(MyClass.x)
print(MyClass.foo)
print(MyClass.__doc__)
print(type(MyClass))


print("```````````````````````")
a = MyClass()
print(a.foo())
print(a.x)
print(a.foo)

print("=============")
print(a.foo())
print(id(a))


结果为:
<class __main__.MyClass>
MyClass
abc
<function MyClass.foo at 0x03B60660>
A example class
<class type>
```````````````````````
62323696
<__main__.MyClass object at 0x03B6FBF0>
abc
<bound method MyClass.foo of <__main__.MyClass object at 0x03B6FBF0>>
=============
62323696
<__main__.MyClass object at 0x03B6FBF0>
62323696

使用上面的语法,在类对象名称后面加上一个括号, 就调用类的实例化方法,完成实例化,实例化就是真正创建一个该类的对象(实例),比如:

 tom = Person()
jerry = Person()

上面的tom、Jerry都是person类的实例,通过实例化后生成了2个实例。每次实例化后获得的实例,是不同的实例,即使是使用相同的参数实例化,也得到不一样的对象。Python类实例话后,会自动调用__init__方法,这个方法第一个参数必须留给self,其他参数随意。

class MyClass:
    """A example class"""
    x = "abc"#类属性
    
    def foo(self):#类属性foo,也是方法,self跟实例有关,
        print(id(self))
        return self
    

print(MyClass.x)
a = MyClass()
print(a.foo())#类的标识符跟实例a进行了绑定
print(a.x)

结果为:
abc
62323760
<__main__.MyClass object at 0x03B6FC30>
abc

__init__方法

MyClass()实际调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用。之前会有__new__方法。

作用:对实例进行初始化。

class MyClass:
    def __init__(self):
        print("init")
    
print(MyClass)#不会调用
print(MyClass())#调用__init__
a  = MyClass()#调用__init__

结果为:
<class __main__.MyClass>
init
<__main__.MyClass object at 0x00000000059D1160>
init

初始化函数可以为多个参数,但是第一个位置必须是留给self的,例如__init__(self,name,age)

class Person:
    x = "abc"#类属性
    def __init__(self,name):
        self.name = name #实例属性
        
a = Person("ton")
b = Person("ton")

print(a.name,b.name)
print(a.x,b.x)
print(a==b)
print(a is b)

结果为:
ton ton
abc abc
False
False

实例化后就算名字相同,也是两个不同的实体。

class Person:
    def __init__(self,name,age):
        self.name = name 
        self.age = age
        
    def showage(self):
        print("{} is {}".format(self.name,self.age))

tom = Person("Tom",20)#实例化
jerry = Person("Je",25)
print(tom.name,jerry.name)
jerry.age+=1
print(jerry.age)
jerry.showage()

结果为:
Tom Je
26
Je is 26

注意:__init__()方法不能有返回值,也就是只能是none。

class Person:
    x = "abc"
    def __init__(self,name,age = 18):
        self.name = name 
        self.y = age#y就是一个标识符而已,可以任意取名
        
a = Person("ton")
b = Person("jerry",20)

print(a.name,b.name)
print(a.x,b.x)

print(a.y,b.y)

结果为:
ton jerry
abc abc
18 20
class Person:
    x = "abc"
    def __init__(self,name,age = 18):
        self.name = name 
        self.y = age#y就是一个标识符而已,可以任意取名
        
    def show(self,x,y):
        print(self.name,x,y)#这个x,y和上面的self.y不是同一个,这个相当于是形参,要后面传进来。
        
a = Person("ton")
b = Person("jerry",20)

print(a.name,b.name)
print(a.x,b.x)

print(a.y,b.y)
a.show(100,"a")

结果为:
ton jerry
abc abc
18 20
ton 100 a
class Person:
    x = "abc"
    def __init__(self,name,age = 18):
        self.name = name 
        self.y = age#y就是一个标识符而已,可以任意取名
        
    def show(self,x,y):
        print(self.name,x,y)#这个x,y和上面的self.y不是同一个,这个相当于是形参,要后面传进来。
        self.y = x
        Person.x = x
        
a = Person("ton")
b = Person("jerry",20)

print(a.name,b.name)
print(a.x,b.x)

print(a.y,b.y)
a.show(100,"a")
print(a.y)
print(Person.x)

结果为:
ton jerry
abc abc
18 20
ton 100 a
100
100

实例对象instance

类实例化后一定会获得一个对象,就是实例对象,上面例子中的tom、jerry就是person类的实例。

__init__方法的第一个参数self就是指代某一个实例。

类实例化后,得到一个实例对象,实例对象会绑定方法,调用方法时采用jerry.showage()的方式,但是这个函数签名是showage(self),少传一个参数吗?

这个self就是jerry,python会把方法的调用者作为第一个参数self的实参传入,self.name就是jerry对象的name,name是保存在了jerry对象上,而不是person类上,所以,称为实例变量。

class MyClass:
    def __init__(self):
        print("self in init = {} ".format(id(self)))

c = MyClass()#会调用__init__方法
print("c = {}".format(id(c)))

结果为:
self in init = 94250320 
c = 94250320

上例说明,self就是调用者,就是c对应的实例对象。self这个名字只是一个惯例,它可以修改,但是请不要修改,不然会影响代码的可读性。

看打印的结果,思考一下执行的顺序,为什么?

class MyClass:
    """A example class"""
    x = "abc"#类属性
    
    def __init__(self):
        print("init")
   
    def foo(self):#类属性foo,也是方法
        return "foo={}".format(self.x)
    
a = MyClass()
print(a.foo())

结果为:
init
foo=abc

实例化的时候,会调用__init__方法初始化。如果没有会隐式的调用。

实例变量和类变量

class Person:
    age = 3
    def __init__(self,name):
        self.name = name 
        
    def showage(self):
        print("{} is {}".format(self.name,self.age))

tom = Person("Tom")#实例化,初始化
jerry = Person("Jerry")

print(tom.name,tom.age)
print(jerry.name,jerry.age)
print(Person.age)
Person.age= 30
print(Person.age,jerry.age,jerry.age)

结果为:
Tom 3
Jerry 3
3
30 30 30

实例变量是每一个实例自己的变量,是自己独有的, 而类变量是类的变量,是类的所有实例共享的属性和方法。

特殊属性:

__name__:对象名

__class__:对象的类型。和type看的内容是一样。

__dict__:对象属性的字典。

__qualname__:类的限定名(自己定义的类的实例没有这个属性)

注意:Python中每一种对象都拥有不同的属性,函数、类都是对象,类的实例也是对象。

class Person:
    x = "abc"
    def __init__(self,name,age = 18):
        self.name = name 
        self.y = age#y就是一个标识符而已,可以任意取名
        
    def show(self,x,y):
        print(self.name,x,y)#这个x,y和上面的self.y不是同一个,这个相当于是形参,要后面传进来。
        self.y = x
        Person.x = x
        
a = Person("ton")
b = Person("jerry",20)

print(a.__class__,b.__class__)
print(a.__class__.__qualname__,a.__class__.__name__)

print(Person.__dict__)
print(a.__dict__)
print(b.__dict__)


结果为:
<class __main__.Person> <class __main__.Person>
Person Person
{__module__: __main__, x: abc, __init__: <function Person.__init__ at 0x03B604B0>, show: <function Person.show at 0x03B604F8>, __dict__: <attribute __dict__ of Person objects>, __weakref__: <attribute __weakref__ of Person objects>, __doc__: None}
{name: ton, y: 18}
{name: jerry, y: 20}

 

class Person:
    age = 3
    def __init__(self,name):
        self.name = name 
        
    def showage(self):
        print("{} is {}".format(self.name,self.age))

print("---------class----------------")
print(Person.__class__)
print(sorted(Person.__dict__.items()),end = "

")#属性字典

tom = Person("Tom")#实例化,初始化
print("-------instance tom--------")
print(tom.__class__)
print(sorted(tom.__dict__.items()),end = "

")

print("-------tom‘s class--------")
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()),end = "

")

结果为:

---------class----------------
<class type>
[(__dict__, <attribute __dict__ of Person objects>), (__doc__, None), (__init__, <function Person.__init__ at 0x0000000005A6DE18>), (__module__, __main__), (__weakref__, <attribute __weakref__ of Person objects>), (age, 3), (showage, <function Person.showage at 0x0000000005A6DD90>)]

-------instance tom--------
<class __main__.Person>
[(name, Tom)]

-------toms class--------
Person
[(__dict__, <attribute __dict__ of Person objects>), (__doc__, None), (__init__, <function Person.__init__ at 0x0000000005A6DE18>), (__module__, __main__), (__weakref__, <attribute __weakref__ of Person objects>), (age, 3), (showage, <function Person.showage at 0x0000000005A6DD90>)]

上例中,可以看到类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,如果从实例访问类的属性,就需要借助__class__找到所属的类。再看下面的例子。

class Person:
    age = 3
    height = 170
    def __init__(self,name,age = 18):
        self.name = name 
        self.age = age

tom = Person("Tom")#实例化,初始化
jerry = Person("Jerry",20)

Person.age = 30
print(Person.age,tom.age,jerry.age)#输出什么结果30,18,20
print(Person.height,tom.height,jerry.height)#输出结果是什么?170,170,170

jerry.height = 175
print(Person.height,tom.height,jerry.height)#输出结果是什么?,为对象新增了一个属性

tom.height+=10
print(Person.height,tom.height,jerry.height)#输出结果是什么?

Person.height+=15
print(Person.height,tom.height,jerry.height)#输出结果是什么?

Person.weight= 70
print(Person.weight,tom.weight,jerry.weight)#输出结果是什么?

print(tom.__dict__["height"])
print(tom.__dict__["weight"])#可以吗?

结果为:
30 18 20
170 170 170
170 170 175
170 180 175
185 180 175
70 70 70
180
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-20-5ca4753173bd> in <module>
     26 
     27 print(tom.__dict__["height"])
---> 28 print(tom.__dict__["weight"])#可以吗?

KeyError: weight

总结:是类的,也是这个类所有实例的,类的实例都可以访问到,是实例的,就是这个实例自己的,通过类是访问不到的。

类变量是属于类的变量,这个类的所有实例可以共享这个变量。

实例可以动态的给自己增加一个属性,实例.__dict__[变量名]和实例.变量名都可以访问到,实例的同名变量会隐藏这类变量,或者说是覆盖了这个类变量。

实例属性的查找顺序

指的是实例使用.来访问属性,它会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找。

注意:如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量,这是指明使用字典的key查找,不是属性查找。

一般来说,类变量使用全大学来命名。

装饰一个类

首先先回顾以下是什么是高阶函数,什么是装饰器函数?然后思考,如何装饰一个类。需求:为一个类通过装饰,增加一些类属性。例子有问题

#增加类变量

def add_name(name,cls):
    cls.name = name#动态增加属性
    
#改进成装饰器
def add_name(name):
    def wrapper(cls):
        cls.name = name 
        return cls
    return wrapper

@add_name("tom")
class Person:
    AGE = 3
    
print(Person.name)

之所以能够装饰,本质上是为类对象动态的添加了一个属性,而Person这个标识符指向这个类对象。

类方法和静态方法

前面的例子中定义的__init__等方法,这些方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类必须实例化之后,由实例来调用这个方法。

普通函数

class Person:
    def normal_method():#可以吗?
        print("normal")
        
#如何调用
Person.normal_method()#可以吗?
print(Person.__dict__)
Person().normal_method()#可以吗?      

结果为:

normal
{__module__: __main__, normal_method: <function Person.normal_method at 0x0000000005A6DA60>, __dict__: <attribute __dict__ of Person objects>, __weakref__: <attribute __weakref__ of Person objects>, __doc__: None}
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-166c70d8d0b6> in <module>
      6 Person.normal_method()#可以吗?
      7 print(Person.__dict__)
----> 8 Person().normal_method()#可以吗?

TypeError: normal_method() takes 0 positional arguments but 1 was given

Person.normal_method()是可以调用的,因为这个方法只是被Person这个名词空间管理的一个普通的方法,normal_method这是Person的一个属性而已。

由于normal_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用。

注意:虽然语法是对的,但是,没有人这么用,也就是说禁止这么写。

类方法

class Person:
    @classmethod
    def class_method(cls):#cls是什么
        print("class = {0.__name__} ({0})".format(cls))
        cls.HEIGHT = 170

        
Person.class_method()
print(Person.__dict__)

结果为:
class = Person (<class __main__.Person>)
{__module__: __main__, class_method: <classmethod object at 0x0000000005A729E8>, __dict__: <attribute __dict__ of Person objects>, __weakref__: <attribute __weakref__ of Person objects>, __doc__: None, HEIGHT: 170}

类方法:

  1. 在类定义中,使用@classmethod装饰器装饰的方法。
  2. 必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身。
  3. cls这个标识符可以是任意合法名称,但是为了易读,请不要修改。
  4. 通过cls可以直接操作类的属性。

注意:无法通过cls操作类的实例。为什么?因为根本看不见类的实例。

类方法,类似于c++,Java中的静态方法。

静态方法

class Person:
    @classmethod
    def class_method(cls):#cls是什么
        print("class = {0.__name__} ({0})".format(cls))
        cls.HEIGHT = 170
     
    @staticmethod
    def static_method():
        print(Person.HEIGHT)
        
Person.class_method()
Person.static_method()
print(Person.__dict__)

class = Person (<class __main__.Person>)
170
{__module__: __main__, class_method: <classmethod object at 0x0000000005A72828>, static_method: <staticmethod object at 0x0000000005A72860>, __dict__: <attribute __dict__ of Person objects>, __weakref__: <attribute __weakref__ of Person objects>, __doc__: None, HEIGHT: 170}

静态方法

  1. 在类定义中,使用@staticmethod装饰器修饰的方法。
  2. 调用时,不会隐式的传入参数。

静态方法,只是表明这个方法属于这个名词空间,函数归在一起,方便组织管理。

方法的调用

类可以定义这么多中方法,究竟应该如何调用它们?

class Person:
    def normal_method():
        print("normal")
        
    def method(self):
        print("{}‘s method".format(self))
    
    @classmethod
    def classmethod(cls):#cls是什么?
        print("class = {0.__name__} ({0})".format(cls))
        cls.HEIGHT = 170
        
    @staticmethod
    def static_method():
        print(Person.HEIGHT)
        
        
print("--------类访问")
print(1,Person.normal_method())#可以吗?
print(2,Person.method())

结果为:
--------类访问
normal
1 None
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-30-a4baf47d5334> in <module>
     18 print("--------类访问")
     19 print(1,Person.normal_method())#可以吗?
---> 20 print(2,Person.method())
     21 

TypeError: method() missing 1 required positional argument: self


print(3,Person.class_method())
结果为:
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-32-d960856de31c> in <module>
----> 1 print(3,Person.class_method())

AttributeError: type object Person has no attribute class_method


print(4,Person.static_method())
结果为:
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-34-b9fa63a96258> in <module>
----> 1 print(4,Person.static_method())

<ipython-input-30-a4baf47d5334> in static_method()
     13     @staticmethod
     14     def static_method():
---> 15         print(Person.HEIGHT)
     16 
     17 

AttributeError: type object Person has no attribute HEIGHT

print(Person.__dict__)
结果为:
{__module__: __main__, normal_method: <function Person.normal_method at 0x0000000005A5DC80>, method: <function Person.method at 0x0000000005A5D950>, classmethod: <classmethod object at 0x0000000005A6EC50>, static_method: <staticmethod object at 0x0000000005A6ECF8>, __dict__: <attribute __dict__ of Person objects>, __weakref__: <attribute __weakref__ of Person objects>, __doc__: None}

print("--------实例访问")
print("tom----------")
tom = Person()
print(1,tom.normal_method())

结果为:
--------实例访问
tom----------
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-38-1d0d497b8a1d> in <module>
      2 print("tom----------")
      3 tom = Person()
----> 4 print(1,tom.normal_method())

TypeError: normal_method() takes 0 positional arguments but 1 was given


print(2,tom.method())
结果为:
<__main__.Person object at 0x00000000059D1550>s method
2 None

print(3,tom.class_method())
结果为:
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-42-238c8e7bb6c0> in <module>
----> 1 print(3,tom.class_method())

AttributeError: Person object has no attribute class_method

print(4,tom.static_method())
结果为:
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-44-edfa7c7fbf2a> in <module>
----> 1 print(4,tom.static_method())

<ipython-input-30-a4baf47d5334> in static_method()
     13     @staticmethod
     14     def static_method():
---> 15         print(Person.HEIGHT)
     16 
     17 

AttributeError: type object Person has no attribute HEIGHT

print("jerry----------")
jerry = Person()
print(1,jerry.normal_method())
结果为:
jerry----------
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-46-023d6a6671c6> in <module>
      1 print("jerry----------")
      2 jerry = Person()
----> 3 print(1,jerry.normal_method())

TypeError: normal_method() takes 0 positional arguments but 1 was given


print(2,jerry.method())
结果为:
<__main__.Person object at 0x0000000005A575F8>s method
2 None

print(3,jerry.class_method())
结果为:
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-51-bb3fdc65cbc8> in <module>
----> 1 print(3,jerry.class_method())

AttributeError: Person object has no attribute class_method

static
print(4,jerry.static_method()) 结果为: --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-52-9c8bc4677435> in <module> ----> 1 print(4,jerry.static_method()) <ipython-input-30-a4baf47d5334> in static_method() 13 @staticmethod 14 def static_method(): ---> 15 print(Person.HEIGHT) 16 17 AttributeError: type object Person has no attribute HEIGHT

类几乎可以调用所有内部定义的方法,但是调用普通的方法时会报错,原因是第一参数必须是类的实例。

实例也几乎可以调用所有的方法,普通的函数的调用一般不可能出现,因为不允许这么定义。

总结:

类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。

实例乐意调用所有类中定义的方法(包括类方法,静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类。

访问控制

私有属性

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self.age = age
        
    def growup(self,i=1):
        if i>0 and i<150:#逻辑控制
            self.age+=i
            
p1 = Person("tom")
p1.growup(20)#正常的范围
print(p1.age)
p1.age = 160#超过了范围
print(p1.age)    

结果为:
38
160

上面的例子,本来是想通过方法控制属性,但是由于属性在外部可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性。

Python提供了私有属性可以解决这个问题。

私有属性:使用双下划线开头的属性,就是私有属性。

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self.__age = age
        
    def growup(self,i=1):
        if i>0 and i<150:#逻辑控制
            self.__age+=i
            
p1 = Person("tom")
p1.growup(20)#正常的范围
print(p1.__age)


结果为:
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-61-80156cc852e4> in <module>
     10 p1 = Person("tom")
     11 p1.growup(20)#正常的范围
---> 12 print(p1.__age)

AttributeError: Person object has no attribute __age

通过实验可以看出,外部已经访问不到__age了,age根本没有定义,更是访问不到。

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self.__age = age
        
    def growup(self,i=1):
        if i>0 and i<150:#逻辑控制
            self.__age+=i
            
    def getage(self):
        return self.__age
            
print(Person("tom").getage())

结果为:
18

私有变量的本质

外部访问不到,能够动态增加一个——-age呢?

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self.__age = age
        
    def growup(self,i=1):
        if i>0 and i<150:#逻辑控制
            self.__age+=i
            
    def getage(self):
        return self.__age
            
p1 = Person("tom")
p1.growup(20)#正常的范围
print(p1.__age)#

结果为:
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-64-569718545c30> in <module>
     13 p1 = Person("tom")
     14 p1.growup(20)#正常的范围
---> 15 print(p1.__age)#

AttributeError: Person object has no attribute __age


p1.__age = 28
print(p1.__age)
print(p1.getage())
#为什么年龄不一样?__age没有覆盖吗?
print(p1.__dict__)

结果为:
28
38
{name: tom, _Person__age: 38, __age: 28}

秘密都在__dict__中,里面是{__age:28,_person__age:38,name;tom}

所以私有变量的本质:类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为_类名__变量名的名称,所以用原来的名字访问不到了。

知道了这个名字,能否直接修改呢?

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self.__age = age
        
    def growup(self,i=1):
        if i>0 and i<150:#逻辑控制
            self.__age+=i
            
    def getage(self):
        return self.__age
            
p1 = Person("tom")
p1.growup(20)#正常的范围
#print(p1.__age)#访问不到

p1.__age = 28
print(p1.__age)
print(p1.getage())
#为什么年龄不一样?__age没有覆盖吗?
print(p1.__dict__)

#直接修改私有变量
p1._Person__age = 15
print(p1.getage())
print(p1.__dict__)

结果为:
28
38
{name: tom, _Person__age: 38, __age: 28}
15
{name: tom, _Person__age: 15, __age: 28}

从上面的例子可以看到,知道私有变量的新名字,就可以直接从外部访问到,并可以修改它。

保护变量

在变量名前使用一个下划线,称为保护变量。

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self._age = age

tom = Person("tom")
print(tom._age)
print(tom.__dict__)

结果为:
18
{name: tom, _age: 18}

可以看出,这个_age属性根本就没有改变名称,和普通的属性一行,解释器不会做任何特殊处理,这只是开发者共同的约定,看见这种变量,就如同私有变量,不能直接使用。

私有方法

参照保护变量、私有变量,使用单下划线、双下划线命名方法。有问题

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self._age = age
        
    def _getname(self):
        return self.name
    
    def __getage(self):
        return self._age

tom = Person("tom")
print(tom._getname())#没改名
#print(tom.__getage())#无此属性
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._person__getage())#改名了

私有方法的本质:

单下划线的方法只是开发者之间的规定,解释器不做任何改变。

双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,_类名__方法名,方法变量都在类的__dict__中可以找到。

私有成员的总结

在Python中使用单下划线或者是双下划线来标识一个成员被保护或者被私有化隐藏起来。但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员,Python中没有绝对的安全的保护成员或者私有成员。

因此,前导的下划线只是一个警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改它们。

补丁

可以通过修改或者替换类的成员,使用者调用的方式并没有什么改变,但是,类提供的功能可能已经改变了。

猴子补丁(monkey patch),在运行时,对属性、方法,函数等进行动态替换,其目的往往为了通过替换、修改来增强、扩展原有代码的能力。这是个黑魔法,要慎用。

例子没有印上来。

属性装饰器

一般比较好的设计是:把实例的属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self.__age = age
        
    def age(self):
        return self.__age
    
    def set_age(self,age):
        self.__age = age

tom = Person("tom")
print(tom.age())
tom.set_age(20)
print(tom.age())


结果为:
18
20

通过age和set_age方法操作属性,有没有简单的方式?

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self.__age = age
   
    @property    
    def age(self):
        return self.__age
    
    @age.setter
    def age(self,age):
        self.__age = age
        
        
    @age.deleter
    def age(self):
        print("del")

tom = Person("tom")
print(tom.age())

tom.age = 20
print(tom.age)
del tom.age

特别注意:使用property装饰器的时候这个方法同名。

property装饰器

后面跟的函数名就是以后的属性名,他就是getter,这个必须有,有了它至少是只读属性。

setter装饰器

与属性名同名,且接收2个参数,第一个是self,第二是将要赋值的值,有了它,属性可写。

deleter装饰器

可以控制是否删除属性,很少用。

property装饰器必须在前,setter,deleter装饰器在后,property装饰器能通过简单的方式,把对方法的操作变成属性的访问你,变起到了一定的隐藏效果。

 

以上是关于python面向对象的主要内容,如果未能解决你的问题,请参考以下文章

python之路之前没搞明白4面向对象(封装)

Python面向对象学习之八,装饰器

python:第二部分:面向对象:面向对象object orinted

面向面试编程代码片段之GC

Python 面向对象

面向对象编程其实很简单——Python 面向对象(初级篇)