zoukankan      html  css  js  c++  java
  • Python基础学习总结(七)

    9.类

      面对对象编程Object Oriented Programming,简称OOP。

      面向对象编程是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。

      在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

      根据类来创建对象被称为实例化。面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

    9.1创建和使用类

      在Python中,首字母大写的名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。我们编写了一个文档字符串,对这个类的功能作了描述。类中的函数称为方法。

      以Student类为例,在Python中,定义类是通过class关键字:

    1 class Student(object):
    2     pass

      class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

    9.1.1创建类

    1 class Student(object):
    2     def __init__(self, name, score):
    3         self.name = name
    4         self.score = score

      1.方法__init__() 是一个特殊的方法,创建新实例时,Python都会自动运行它。开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。这个方法的定义中,形参self 必不可少,还必须位于其他形参的前面

      2.每个与类相关联的方法调用都自动传递实参self ,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。self 会自动传递,因此我们不需要传递它。

      3.以self 为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。

      4.self.name= name像这样可通过实例访问的变量称为属性

      5.面向对象编程的一个重要特点就是数据封装。可以直接在类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法.

    9.1.2根据类创建实例

      我们通常可以认为首字母大写的名称(如Dog )指的是类,而小写的名称(如my_dog )指的是根据类创建的实例。

      1、要访问实例的属性,可使用句点表示法,我们编写了如下代码来访问my_dog 的属性name 的值。

      my_dog.name  , 句点表示法在Python中很常用,这种语法演示了Python如何获悉属性的值。

      2、根据Dog 类创建实例后,就可以使用句点表示法来调用Dog 类中定义的任何方法。

      3、可按需求根据类创建任意数量的实例。

    9.2使用类和实例

      1.你需要执行的一个重要任务是修改实例的属性。你可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。

      2.类是创建实例的模板,而实例则是一个个具体的对象,各个实例拥有的数据都互相独立,互不影响;方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同。

    9.2.1给类设定初始值

          类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法__init__() 内指定这种初始值是可行的;如果你对某个属性这样做了,就无需包含为它提供初始值的形参。

    直接在class中定义属性,这种属性是类属性:

    1 class Student(object):
    2     name = 'Student'

      在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

    9.2.2修改属性的值

      可以以三种不同的方式修改属性的值:

      1.直接通过实例进行修改;

      2.通过方法进行设置;

      3.通过方法进行递增(增加特定的值)。

    9.2.3访问限制

      1.在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

      2.如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

    1 class Student(object):
    2     def __init__(self, name, score):
    3         self.__name = name
    4         self.__score = score
    5  
    6     def print_score(self):
    7         print('%s: %s' % (self.__name, self.__score))

      3.改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score了:

    >>> bart = Student('Bart Simpson', 98)
    >>> bart.__name
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute '__name'

      4.这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。但是如果外部代码要获取name和score,可以给Student类增加get_name和get_score这样的方法:

    1 class Student(object):
    2     ...
    3     def get_name(self):
    4         return self.__name
    5  
    6     def get_score(self):
    7         return self.__score

      5.如果又要允许外部代码修改score,可以再给Student类增加set_score方法:

    1 class Student(object):
    2     ...
    3     def set_score(self, score):
    4         self.__score = score

      6.和原来直接调用参数相比,在方法中,可以对参数做检查,避免传入无效的参数:

    1 class Student(object):
    2     ...
    3     def set_score(self, score):
    4         if 0 <= score <= 100:
    5             self.__score = score
    6         else:
    7             raise ValueError('bad score')

      7.需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

      8.双下划线开头的实例变量也不是一定不能从外部访问。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量。

    9.3继承

      1.如果一个类似另一个类的特殊版本,可以使用继承,一个类继承另一个类它将自动获取另一个类的所有属性和方法。原有的类为父类,新类为子类。

      2.子类继承父类所有的属性和方法,还可以定义自己的属于和方法。在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

    1 class Dog(Animal):  #继承Animal
    2     pass

    9.3.1子类的方法__init__()

      1.继承需要给父类的所有属性赋值,子类的__init__()需要父类施以援手。

      2.并且父类必须在继承文件中,在子类之前。

      3.定义子类时,必须在括号中指定父类的名称。

      4.super()特殊函数,帮助python将父类和子类并联起来。父类也称超类,super的由来。

    9.3.2定义子类的方法和属性

      让一个类继承一个类后。可添加区别子类和父类的属性和方法。

    9.3.3重写父类

      对应父类的方法,只有不符合子类的需要都可以重写,在子类中添加新的方法描述子类的特点。去父类的去其糟怕取其精华。

    9.3.4多态

      1.当子类和父类都存在相同的方法时,我们说,子类覆盖了父类的方法,在代码运行的时候,总是会调用子类的方法。这样,我们就获得了继承的另一个好处:多态

      2.所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行。

      3.多态的好处是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思。

      4.对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

    • 对扩展开放:允许新增Animal子类;
    • 对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

    9.3.5使用__slots__

      为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性。

    1 class Student(object):
    2     __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

      使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。

    9.3.6多重继承

      1.通过多重继承,一个子类就可以同时获得多个父类的所有功能。

      2.设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich(鸵鸟)继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

      3.这样我们就不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。只允许单一继承的语言(如Java)不能使用MixIn的设计。

    9.3.7定制类

      1.Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。

      __str__

      定义好__str__()方法,可以返回一个好看的字符串就:

    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...     def __str__(self):
    ...         return 'Student object (name: %s)' % self.name
    ...
    >>> print(Student('Michael'))
    Student object (name: Michael)

      这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。

      直接敲变量不用print,打印出来的实例还是不好看:

    >>> s = Student('Michael')
    >>> s
    <__main__.Student object at 0x109afb310>

      这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回对用户友好的字符串,而__repr__()返回对程序员开发者友好的字符串,也就是说,__repr__()是为调试服务的。

      解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:

    1 class Student(object):
    2     def __init__(self, name):
    3         self.name = name
    4     def __str__(self):
    5         return 'Student object (name=%s)' % self.name
    6     __repr__ = __str__

      __iter__

      如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

    我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

     1 class Fib(object):
     2     def __init__(self):
     3         self.a, self.b = 0, 1 # 初始化两个计数器a,b
     4 
     5     def __iter__(self):
     6         return self # 实例本身就是迭代对象,故返回自己
     7 
     8     def __next__(self):
     9         self.a, self.b = self.b, self.a + self.b # 计算下一个值
    10         if self.a > 100000: # 退出循环的条件
    11             raise StopIteration()
    12         return self.a # 返回下一个值

      现在,试试把Fib实例作用于for循环:

    >>> for n in Fib():
    ...     print(n)
    ...
    1
    1
    2
    3
    5
    ...
    46368
    75025

      __getitem__

      Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:

    >>> Fib()[5]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'Fib' object does not support indexing

      要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:

    1 class Fib(object):
    2     def __getitem__(self, n):
    3         a, b = 1, 1
    4         for x in range(n):
    5             a, b = b, a + b
    6         return a

      __getattr__

      Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值。已有的属性,比如name,不会在__getattr__中查找。

      __call__

      1.任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。

      2.通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。

    9.3.8枚举类

      为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:

    1 from enum import Enum
    2 Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

    9.3.9元类

      type()

      要创建一个class对象,type()函数依次传入3个参数:

    1. class的名称;
    2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法,加个逗号;
    3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

      metaclass

      metaclass,直译为元类,简单的解释就是:当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

    __new__()方法接收到的参数依次是:

    1. 当前准备创建的类的对象;
    2. 类的名字;
    3. 类继承的父类集合;
    4. 类的方法集合。

    9.3.10将实例用作属性

      使用代码模拟实物时,当你添加越来越多的特点和细节是,会发现属性和方法以及代码文件都越来越长,这时候可以把其中一部分分离,重新组成一个类。可以将一个大类分成许多小类。

    9.3.11模拟实物

      模拟较复杂的实物,需要从较高的逻辑层次考虑。为了编写效率更高,更简洁准确的代码,可能需要不停的重组类。

    9.4导入类

      1.随着不断对类的添加,即使很优秀的继承也会是代码变得很长,所以python可以让你把类导入模块。并在主程序中导入使用所需要的模块。

      2.为自己的每个模块写文档字符串,说明模块的作用,对内容进行简要叙述。

      3.从一个模块导入多个类,用逗号隔开,并根据自己的需要创建实例。

      4.也可以导入整个类,并在实例前加上类名,和访问需要的类。

      5.在一个模块中导入另一个模块。

    9.5python标准库(模块)

      Python标准库是一组模块,只需要import语句就可以导入使用。

      字典让你把信息关联起来,但不记录你添加键值对的顺序,要创建字典并记录添加键值对的顺序,可使用模块collections中的orderedDict类。OrderedDict的实例和字典一样,不过记录了添加键值对的添加顺序。

      OrderdDict很不错,兼具列表和字典的特点,有时候很需要。

      random模块

      包含各种方式生成随机数的函数。其中randint()返回一个位于指定范围内的整数。

      datetime是模块

      datetime模块还包含一个datetime类,通过from datetime import datetime导入的才是datetime这个类。如果仅导入import datetime,则必须引用全名datetime.datetime。

      datetime.now()返回当前日期和时间,其类型是datetime。

      常用的第三方库

      还有MySQL的驱动:mysql-connector-python,用于科学计算的NumPy库:numpy,用于生成文本的模板工具Jinja2,等等。

      1.urlparse 模块,urlpasrse 模块提供了一些基本功能,用于处理 URL 字符串。这些功能包括 urlparse()、 urlunparse()和 urljoin()。

      2.urlparse()将 urlstr 解析成一个 6 元组(prot_sch, net_loc, path, params, query, frag)。前面已 经描述了这里的每个组件。

      3.urlunparse()的功能与 urlpase()完全相反,其将经 urlparse()处理的 URL 生成 urltup 这个 6 元组(prot_sch, net_loc, path, params, query, frag),拼接成 URL 并返回。

      4.urllib 模块提供了许多函数,可用于从指定 URL 下载数据,同时也可以对字符串进行编 码、解码工作,以便在 URL 中以正确的形式显示出来。下面将要介绍的函数包括 urlopen()、 urlretrieve()、quote()、unquote()、quote_plus()、unquote_plus()和 urlencode()。

      5.urlopen()打开一个给定 URL 字符串表示的 Web 连接,并返回文件类型的对象。

      6.urlretrieve()不是用来以文件的形式访问并打开 URL,而是用于下载完整的 HTML,把另存为文件。

    9.6类编码风格

      熟悉与类相关的编码风格,当程序复杂时更应该这样。

      1.类名应采用驼峰命名法,即类的首字母大写,不含下划线,而实例名和模块名都首字母小写,并用下划线连接单词。

      2.在每个类定义后面添加文档字符串说明,简要描述类的功能作用,并且在每个模块下面都要添加文档字符串说明,对模块下的类可以做什么进行简要说明。

      3.用空行来组织代码,但不要滥用,在类中可用一个空行分隔方法,用两个空行分隔类。

      4.需要同时导入标准库中的模块和自己编写的模块时,先编写导入标准库模块的import语句,用一个空行隔开,在导入自己编写的模块import语句。在包含多个import语句时,可以让人容易弄明白程序中各个模块的由来。

  • 相关阅读:
    第三周作业
    第二周作业
    第一周作业附加
    第三次结构部分作业
    第二次作业
    最后一周作业
    第14,15周作业
    第七周作业
    第六周作业
    第四周作业
  • 原文地址:https://www.cnblogs.com/zt19994/p/7157320.html
Copyright © 2011-2022 走看看