Python类

Python类

类的基本概念

创建一个类:

class Person:
    """
    This is a sample of class
    """
    def __init__(self, name):
        self.name = name
    def get_name(self):
        return self.name
    def color(self, color):
        d = {}
        d[self.name] = color
        return d

解释:class Person,声明创建一个名为“Person”的类。类的名称一般用大写字母开头。在Python3中所有的类都是object的子类,但是可以不用显式写出来,如果要继承其他非object的父类时,要在类名后面用括号跟上父类名字,即class Person(FatherClass)。
类里面的代码是三个函数,但是它们的参数中都有self,这是和Python中的函数不一样的地方,类中的函数被称作“方法”。方法的参数列表中必须包含self参数,并且默认作为第一个参数。
def __init__(self, name),它是一个特殊的方法,方法名用双下划线开头和结尾,它被称为”初始化方法“,对这个类进行初始化,让这个类有一个基本的面貌。self.name = name的含义是建立实例的一个属性,这个属性的名字是name(指的是第一个name),和参数name的名字是一样的,参数name是在这个类实例化时传入的,注意二者的区别。特别注意__init__()没有return语句
def get_name(self)和def color(self, color)是类里面的另外两个方法,除了第一个参数是self外,其它和函数并没有区别。

类的实例对象:

if __name__ == "__main__":
    girl = Person("xiaohong")
    print(girl.name)
    name = girl.get_name()
    print(name)
    her_color = girl.color("white")
    print(her_color)

解释:girl = Person(“xiaohong”)创建了一个girl实例,创建实例的过程就是调用类Person()。创建的过程中首先执行初始化方法__init__(),self是默认参数,不需要传值,参数name获得了字符串"xiaohong"的引用,通过参数name得到实例属性self.name=“xiaohong”。
girl.get_name()通过实例girl来调用get_name方法,这个方法返回了实例属性self.name的值,所以print(name)的结果是xiaohong。girl.color(“white”)实例girl调用color方法的时候传入一个参数"white",因为类中定义该方法的时候参数列表里有color,print(her_color)的结果是{‘xiaohong’:‘white’}。
类和实例的关系:

类提供默认行为,是实例的工厂(From Learning Python)

类属性和实例属性

类属性

class Girl:
    breast = 90

breast就是类Girl的属性。这种属性本质上就是类中的变量,它的值不依赖于任何实例,只是由类中所写的变量赋值语句所确定,所以这个类属性也叫静态变量或静态数据。对这个类属性可以进行修改、删除。

Girl.breast = 100  #修改类属性
del Girl.breast  #删除类属性
Girl.height = 165  #增加类属性

使用dir()查看类Girl的属性和方法。

In [5]: dir(Girl)
Out[5]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'breast']

双下划线开头和结尾的是类的特殊属性,通常情况下特殊属性不需要修改。几种特殊属性的含义如下:

  • C.__name__:以字符串的形式返回类的名字
  • C.__doc__:显示类的文档
  • C.__base__:类C的所有父类
  • C.__dict__:以字典的形式显示类的所有属性
  • C.__module__:类所在的模块
In [6]: Girl.__dict__
Out[6]: 
mappingproxy({'__dict__': <attribute '__dict__' of 'Girl' objects>,
              '__doc__': None,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Girl' objects>,
              'breast': 90})

In [7]: Girl.__base__
Out[7]: object

In [8]: Girl.__module__
Out[8]: '__main__'

创建实例
调用类即可创建实例。

In [2]: Girl()
Out[2]: <__main__.Girl at 0xc9ab550>
In [3]: xiaohong = Girl()
In [4]: xiaohong
Out[4]: <__main__.Girl at 0xc9ab2e8>

仅仅写Girl()也是创建了一个实例对象,xiaohong = Girl()意思是变量xiaohong与实例对象Girl()建立了引用关系,类似于赋值语句x = 3。创建实例的过程如下:

  1. 创建实例对象。
  2. 检查是否有__init__()方法。如果没有,则返回实例对象;有则进入下一步。
  3. 调用__init__()方法,将实例对象作为第一个参数self传递进去。
    再次注意下__init__()初始化方法,它的第一个参数必须是self,不能有return语句。

实例属性

In [5]: class A:
   ...:     x = 1
   ...:     
In [6]: foo = A()  #实例化
In [7]: A.x  #类属性
Out[7]: 1
In [8]: foo.x  #实例属性
Out[8]: 1
In [9]: foo.y = 2  #新建实例属性
In [10]: foo.y
Out[10]: 2
In [11]: foo.x += 1  #修改实例属性
In [12]: foo.x
Out[12]: 2
In [13]: del foo.x  #删除修改后的实例属性
In [14]: foo.x
Out[14]: 1
In [15]: A.x += 1  #修改类属性
In [16]: A.x
Out[16]: 2
In [17]: foo.x  #实例属性也跟着改变
Out[17]: 2

实例属性和类属性最大的不同在于,实例属性可以随意更改。这不会对类属性产生影响,因为类属性和类绑定。实例属性可以从类属性处获得比如foo.x,也可以自己创建比如foo.y。对实例属性的修改本质上是新建了一个新的属性foo.x,与原先的foo.x重名,覆盖了原先的foo.x,如果删除修改后的实例属性foo.x,就得到了初始的foo.x。如果修改类属性,实例属性也会跟着改变。
形象地说,就像工厂生产模型,模具(类)的形状和大小是不变的,利用模具生产出来的模型(实例)为了满足不同人群的需求,会有不同的颜色,不同的装扮,既包含了模具的形状大小等特征,又有自身的特色。如果模具的特征改变了,模型的特征也会跟着改变。
可变对象和不可变对象:可变对象所指向的内存中的值可以被改变,不可变对象所指向的内存中的值不能被改变。在Python中,数值类型(int和float)、字符串str、元组tuple都是不可变类型;而列表list、字典dict、集合set是可变类型。(详细说明见Python中的可变对象和不可变对象
以上类属性和实例属性的关系是针对类中变量引用的是不可变对象,如果类中变量引用的是可变对象,类属性和实例属性直接修改这个对象。

关于self:

In [18]: class Person:
    ...:     def __init__(self, name):
    ...:         self.name = name
    ...:         print(self)
    ...:         print(type(self))
    ...:         

In [19]: girl = Person("xiaohong")
<__main__.Person object at 0x000000000C9D06A0>
<class '__main__.Person'>
In [20]: girl
Out[20]: <__main__.Person at 0xc9d06a0>
In [21]: type(girl)
Out[21]: __main__.Person

由上面的内存地址可以看到,self和girl引用的是同一个实例对象,只不过self在类里面,girl在类外面。

方法

类中的方法除了第一个参数是self,本质上和函数没有区别。
如果类和外部的函数对象有联系,会造成类和函数的耦合性太强,不便于维护,此时会用到两个有些特别的方法:类方法和静态方法。

  • 类方法:在类里面定义,它由装饰器@classmethod所装饰,第一个参数cls所引用的是所在类的类对象。
  • 静态方法:在类里面定义,由装饰器@staticmethod所装饰,不以self为第一个参数,仅仅是一个普通的函数。在类里面使用它的时候,通过实例调用self.staticfunction。

类方法

class Foo:
    lang = "Java"  #类属性
    def __init__(self):
        self.lang = "Python"  #实例属性
    
    @classmethod
    def get_class_attr(cls):#参数是含有lang属性的类
        return cls.lang  #此处是类属性
if __name__ == "__main__":
    print(Foo.lang)
    print(Foo.get_class_attr())
    f = Foo()
    print(f.lang)
    print(f.get_class_attr())

#result: Java,Java,Python,Java

静态方法

import random
class Foo:
    def __init__(self, name):
        self.name = name
    def get_name(self, age):
        if self.select(age):  #实例调用静态方法
            return self.name
        else:
            return "the name is secret"
    
    @staticmethod
    def select(n):  #静态方法
        a = random.randint(1,100)
        return a - n > 0

if __name__ == "__main__":
    f = Foo("haha")
    name = f.get_name(22)
    print(name)

继承 多态 封装

(1) 继承
继承的特点是将父类的方法和属性全部承接到子类中;如果子类重写了父类的方法,就使用子类的该方法,父类的被遮盖。
一种比较贪心的情况,重写了父类的方法,但是还想继续使用父类的该方法。方法如下:

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

class Girl(Person):
    def __init__(self, name):
        Person.__init__(self, name)  #以类方法的方式调用父类中的初始化方法
        self.real_name = "balala"
    def get_name(self):
        return self.name

if __name__ == "__main__":
    hong = Girl("xiaohong")
    print(hong.real_name)
    print(hong.get_name())

#result: balala xiaohong

从上面的代码可以看出,直接显式地调用父类中的方法即可。但是这样写容易犯错,如果程序复杂子类和父类之间间隔很远,修改了父类的名字,很容易忘记修改子类中该父类的名字,所以作如下改进:

class Girl(Person):
    def __init__(self, name):
        #Person.__init__(self, name)
        super().__init__(name)  #python2.x为super(Girl, self).__init__(name)
        self.real_name = "balala"
    def get_name(self):
        return self.name

不用父类的名字,而是用super,可得到同样的结果。关于super()的使用,参考Python: 你不知道的 super

当继承多个父类时,就要考虑继承顺序的问题。Python会按照特定的顺序遍历继承图,这个顺序叫方法解析顺序(Method Resolution Order, MRO),类都有一个名为__mro__的特殊属性,使用print(C._mro_)可以打印出类的继承顺序。
Python3中的继承顺序原则是“广度优先”,再深挖的话就是MRO背后的算法了,有兴趣的读者自行百度,但是MRO列表遵循下面三条准则:Python(面向对象编程4——继承顺序、封装)

  1. 子类会先于父类被检查
  2. 多个父类会根据它们在列表中的顺序被检查
  3. 如果对下一个类存在两个合法的选择,选择第一个父类

(2) 多态
多态表示同一种行为具有不同的表现形式。如下面的lambda函数,并没有限制参数的类型,表明同一个函数可以对用于不同的对象,体现了多态。

In [4]: f = lambda x,y : x+y
In [5]: f(2,3)
Out[5]: 5
In [6]: f("Nan","jing")
Out[6]: 'Nanjing'

对于Java和Python的多态特征,区别在于Python不检查传入对象的类型。所以Java是属于“强类型”,Python属于“弱类型”。

(3) 封装
封装是对具体对象的一种抽象,即将某些部分隐藏起来,在程序外部看不到。比如一台主机,就是一个封装起来的对象,使用者不需要知道内部的构造是怎样,只需要通过USB网口等接口与内部进行数据传输即可。
在Python的类中如果不让外部访问内部属性和方法,就要“私有化”,在准备私有化的属性(包括方法、数据)名字前面加双下划线即可。

class ProtectMe:
    def __init__(self):
        self.me = "gg"
        self.__name = "kk"  #内部属性
    def __python(self):  #内部方法
        print("I like Python")
    def code(self):
        print("Which language do you like?")
        self.__python()

几个特殊方法

(1) 属性拦截

  • __setattr__(self,name,value): 如果要给name赋值,调用此方法。
  • __getattr__(self,name): 如果name被访问,同时它不存在,调用此方法。
  • __getattribute(self, name)__: 当name被访问时自动被调用,无论name是否存在。
  • __delattr__(self,name): 如果要删除name,调用此方法。

重写这些方法可以有效地避免程序运行输出异常,拦截异常的属性。

(2) 迭代器
__iter__()是对象的一个特殊方法,可以说明对象是否是可迭代的。

In [19]: lst = [1,2,3,4]
In [20]: iter_lst = iter(lst)
In [21]: iter_lst
Out[21]: <list_iterator at 0xc97de10>
In [22]: hasattr(lst,"__iter__")
Out[22]: True
In [23]: hasattr(iter_lst,"__iter__")
Out[23]: True
In [24]: hasattr(lst,"__next__")
Out[24]: False
In [25]: hasattr(iter_lst,"__next__")
Out[25]: True

列表是可迭代的,但不是迭代器。有__iter__()和__next__()的对象才是迭代器

参考资料:
《跟老齐学Python轻松入门》
Python中的可变对象和不可变对象
Python: 你不知道的 super
菜鸟教程
Python’s super() considered super!
Python(面向对象编程4——继承顺序、封装)

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值