python基础教程(知识点提取)

第 6 章 抽象

1.给函数编写文档

给函数编写文档,以确保其他人能够理解,可添加注释(以#打头的内容)。还有另一种编写注释的方式,就是添加独立的字符串。放在函数开头的字符串称为文档字符串(docstring),将作为函数的一部分存储起来。
例子:

def square(x):
    '计算给定x的平方值'
    return x * x

print(square(10))
print(square.__doc__)

#out
100
计算给定x的平方值

数学意义上的函数总是返回根据参数计算得到的结果。在Python中,有些函数什么都不返回。但在Python中,函数就是函数,即使它严格来说并非函数。什么都不返回的函数不包含return语句,或者包含return语句,但没 有在return后面指定值。

def test():
    print('This is printed')
    return   #return语句只是为了结束函数
    print('This is not')

x = test()
print(x)

#输出  直接跳过第二个print
This is printed
None
#python中所有的函数都返回值,如果没有指定返回什么,就将返回None
2.参数魔法

这里只介绍收集参数和分配参数

2.1 收集参数

python允许向函数提供任意数量的参数,只需遵循下面的定义:

def print_params(*params):
    print(params)

print_params('bozhou')
print_params('bozhou', 'anyi', 'zzh')

#输出
('bozhou',)              #括号里面有个逗号,说明返回的结果为一个元组类型结果
('bozhou', 'anyi', 'zzh')      #这个结果就更明显了

进一步示例:

def print_params_2(want, *params):
    print(want)
    print(params)

print_params_2('hello', 5, 2, 1)

#输出
hello
(5, 2, 1)  #参数前带星号,意味着将余下的值收集为一个元组

进一步

def in_the_middle(x, *y, z):
    print(x, y, z)

in_the_middle(1, 2, 3, 4, z=10)    #也可以放在其他位置,就像本例一样要指定后续参数名称以便区分,否则报错

#输出
1 (2, 3, 4) 10

收集关键字参数,可使用两个星号

def print_params_3(**params):
    print(params)

print_params_3(x=1, y=2, z=3)
#输出一个字典而不是元组
{'z': 3, 'x': 1, 'y': 2}

进一步可结合使用,如:

def print_params_4(x, y, z=3, *pospar, **keypar):
    print(x, y, z)
    print(pospar)
    print(keypar)

print_params_4(1, 2, 3, 4, 5, a=10, b=20)
#输出
1 2 3
(4, 5)
{'a': 10, 'b': 20}
2.2 分配参数

执行与收集参数相反的操作,用例子来说明什么是相反的操作:

def add(x, y):
    return x + y

params = (1, 2)
print(add(*params))

#输出
3

进一步

#本例是只在调用的时候使用了星号
def fun(name, age):
    print('name:', name)
    print('age:', age)

params = {'name': 'zzh', 'age': 25}
fun(**params)
#输出
name: zzh
age: 25

#函数定义和调用都使用星号
def fun1(**params):
    print('name:', params['name'])
    print('age:', params['age'])

fun1(**params)
#输出
name: zzh
age: 25

#普通传参
def fun1(params):
    print('name:', params['name'])
    print('age:', params['age'])

fun1(params)
#输出
name: zzh
age: 25

本小节中,星号被称为拆分运算符。

3 作用域

作用域:变量存储在作用域(也叫命名空间)中。在Python中,作用域分两大类:全局作用域和局部作用域。作用域可以嵌套。
变量到底是什么呢?可将其视为指向值的名称。因此,执行赋值语句x = 1后,名称x指向值1。这几乎与使用字典时一样(字典中的键指向值),只是你使用的是“看不见”的字典。实际上,这种解释已经离真相不远。有一个名为vars的内置函数,它返回这个不可见的字典:

x = 1
scope = vars()
print(type(scope))
print(scope['x'])
#输出

1
#这种“看不见的字典”称为命名空间或作用域

读取全局变量的值通常不会有问题,但还是存在出现问题的可能性。如果有一个局部
变量或参数与你要访问的全局变量同名,就无法直接访问全局变量,因为它被局部变量遮
住了。
如果需要,可使用函数globals来访问全局变量。这个函数类似于vars,返回一个包含全局变量的字典。(locals返回一个包含局部变量的字典)。当函数内外有两个变量名称相同时,必要时可以使用以下方式在函数内部访问函数外的那个全局变量:

globals()['parameter']

待续```````````````````

第 7 章 再谈抽象

开篇定义
多态:可对不同类型的对象执行相同的操作
封装:对外部隐藏有关对象工作原理的细节
继承:可基于通用类创建出专用类

1 对象魔法
1.1 多态

多态,即多种形态,即便你不知道变量(参数变量)指向的是哪种对象,也能够对其执行操作,且操作的行为将随对象所属的类型(类)而异
示例:

#可假设本例为获取狗狗的体重
def get_something(object):
    if isinstance(object, tuple):          #当以元组的形式传入时,如('哈士奇', 50)      
        return object[1]
    #元组的形式过于固定,因为小狗的体重是变化的
    #可以字典的形式传入,将体重单独存在名称为“体重”的key下,这样更加灵活
    elif isinstance(object, dict):       
        return int(object['height'])
#可以看到一个功能函数,对应两个甚至多个不同类型的对象都可以进行操作

进一步,还有更多的例子:

>>> 'abc'.count('a')
1
>>> [1, 2, 'a'].count('a')
1
#无需知道它是字符串还是列表就能调用方法count:只要你向这个方法
提供一个字符作为参数,它就能正常运行。

不仅仅是函数,python中的运算符也体现出多态的特点,如:

>>> 1 + 2
3
>>> 'zzh' + 'bozhou'
'zzhbozhou'
这里讨论的多态形式是Python编程方式的核心,有时称为鸭子类型。这个术语源自如下说法:“如果走起来像鸭子,叫起来像鸭子,那么它就是鸭子。
1.2 封装

在程序设计中,封装(Encapsulation)是对具体对象的一种抽象,即将某些部分隐藏起来,在程序外部看不到,其含义是其他程序无法调用。

封装数据的主要原因是:保护隐私(把不想别人知道的东西封装起来)
封装方法的主要原因是:隔离复杂度

封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口

第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
第二层次的封装:是指把类中的属性和方法私有化,只供内部使用,也可称为隐藏,但不是真正的隐藏

以上两点下面会提到

1.3 继承

对一个通用类进行拓展产生子类,但程序员不想把代码再写一遍,所以对类继承财产并发扬光大的功能就产生了

2 类
2.1 隐藏

要让方法或属性成为私有的(不能从外部访问),只需让其名称以两个下划线打头即可:

class Secretive:
    def __inaccessible(self):
        print("Bet you can't see me ...")
    def accessible(self):   
        print("The secret message is:")
        self.__inaccessible()
o = Secretive()
o.accessible()

#输出
The secret message is:
Bet you can't see me ...

#可以看到仍然是可以用其他方式访问的,并不是真正意义上的隐藏
2.2 继承

至于什么是继承,怎么继承这里就不多叙述了..............
我们来看看如何查看一个类是否是另一个类的子类,python已经提供了内置方法,例子如下:

#类定义就不写了
>>> issubclass(son, father)
True
#再者如果想查看一个类的基类,可访问其特殊属性__bases__
son.__bases__

要确定一个对象是否是特定类的实例,可使用isinstance,用法同上

进一步

x = son()
#所有son类的对象实例都是类father的对象实例,因为son是father的子类,
这时候使用isinstance可能不是精准的,下面小节中会讲到抽象基类。
2.3 多个超类

一个类的父类可以有多个,如何继承语法形式如下:

#多重继承
class son(father1, father2):
    pass

需要注意的是:
使用多重继承时,有一点务必注意:如果多个超类以不同的方式实现了同一个方法(即有多个同名方法),必须class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面在的类的方法

2.4 接口和内省

类中的方法属性都是类的协议接口,多态和封装的思想使我们只关心怎样使用接口,并不关系其中的细节,但python也提供了了解接口的方法,如查看、获取、设置:

class son:
    temp = 10
    def fun(self, a):
        self.name = a 
        print('my name is: ', self.name)

o = son()
print(hasattr(o, 'fun'))
print(getattr(o, 'fun'))
print(getattr(o, 'fun1', None))
setattr(o, 'temp', 7)
print(o.temp)
#输出
True
>
None
7
2.5 抽象基类

一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现或者说必要的一组抽象方法,示例如下:
Python通过引入模块abc提供了官方解决方案,这个模块为所谓的抽象基类提供了支持。

from abc import ABC, abstractmethod
class Talker(ABC):
    @abstractmethod      #将方法标记为抽象的——在子类中必须实现的方法
    def talk(self):
        pass
抽象类(即包含抽象方法的类)最重要的特征是不能实例化
如果继承的子类没有重写talk方法,则该子类也是抽象类,不能进行实例化否则报错!
#正确做法
class Knigget(Talker):
    def talk(self):
        print("Ni!")
现在可以进行实例化,而且只有在这种情形下使用isinstance才是比较精确的,
这种情况下,我们可以相信这个实例有对应的方法(如本例talk方法),相对
于手工检测方法抽象基类是个更好的选择

因为在处理编程和对象时,强调构成问题而不是身份问题,强调hasattr函数而不是isinstance函数(而前者是更精确的)

第 9 章 魔法方法、特性和迭代器
1 元素访问

在Python中,协议通常指的是规范行为的规则。协议指定应实现哪些方法以及这些方法应做什么,类似于接口。序列和映射基本上是元素(item)的集合。

- __len__(self):这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数
- __getitem__(self, key):这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数,其中n为序列的长度
- __setitem__(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取
- __delitem__(self, key):这个方法在对对象的组成部分使用__del__语句时被调用,应删除与key相关联的值
class temp:
    def __init__(self, value):
        self.value = value
        self.seq = {}

    def __len__(self):
        return len(self.seq)

    def __getitem__(self, key):
        print('i am getitem!')
        return self.seq[key]

    def __setitem__(self, key, value):
        print('i am setitem!')
        self.seq[key] = self.value

b = temp(100)
b[1] = 10                #对self.[key]进行赋值操作时调用__getitem__
print(b[1])              #以self[key]的方式获取值时调用__setitem__                        
print(len(b)) 
        
#输出、由输出可看到调用了哪个方法
i am setitem!
i am getitem!
100
1
函数 property

示例:

class Rectangle:
    def __init__ (self):
        self.width = 0
        self.height = 0
    def set_size(self, size):
        self.width, self.height = size
    def get_size(self):
        return self.width, self.height
    
r = Rectangle()
r.width = 10
r.height = 5
print(r.get_size())
print(r.set_size((10, 20)))
print(r.width)

#输出
(10, 5)
None
10

使用property函数

通过调用函数property并将存取方法作为参数(获取方法在前,设置方法在后)
创建了一个新特性(如果我们需要这个特性的话,property给我提供了一个方便
的实现,否则我们还要重新写类方法),然后将名称size关联到这个特性;
class Rectangle:
    def __init__ (self):
        self.width = 0
        self.height = 0
    def set_size(self, size):
        self.width, self.height = size
    def get_size(self):
        return self.width, self.height
    size = property(get_size, set_size)    
    
r = Rectangle()
r.width = 10
r.height = 5
print(r.size)               #r.size会触发get_size方法
r.size = 150, 100     #赋值会触发set_size方法
print(r.width)

#输出
(10, 5)
150

property形式
class property([fget[, fset[, fdel[, doc]]]])
fdel参数 -- 删除属性值函数
doc参数 -- 属性描述信息
python基础教程(知识点提取)_第1张图片
访问控制
__getattr__(self, name)
当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔
法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属
性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理属
性错误。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正
的封装的办法
------------------------------------------------------------------------
__setattr__(self, name, value)
和 __getattr__ 不同, __setattr__ 可以用于真正意义上的封装。它允许你自定义某
个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变
化都定义自己的规则。然后,一定要小心使用 __setattr__ ,这个列表最后的例子
中会有所展示
-------------------------------------------------------------------------
__getattribute__(self, name):在属性被访问时自动调用
-------------------------------------------------------------------------
__delattr__(self, name):试图删除属性时自动调用
2 迭代器

迭代(iterate)意味着重复多次,就像循环那样。已知的for循环迭代序列和字典,但实际上也可迭代其他对象:实现了方法__ iter__的对象;
方法__ iter__返回一个迭代器(可迭代对象),前提是该对象包含方法__ next__ ;当你调用方法__ next__时,迭代器应返回其下一个值。如果迭代器没有可供返回的值,应引发StopIteration异常;

使用 iter() 函数调用,以及在类似 for x in container: 的循环时被调用
class Fibs:
    def __init__(self):
        self.a = 0
        self.b = 1
    def __next__(self):
        print("b")
        self.a, self.b = self.b, self.a + self.b
        return self.a
    def __iter__(self):
        print("a")
        return self

b = Fibs()
print("start!")
for f in b:
    print("d")
    if f > 2:
        print("mark")
        print(f)
        break

#输出,通过输出字母的顺便来看是方法触发的顺序
start!
a
b
d
b
d
b
d
b
d
mark
3
迭代与使用列表相比,在某些情况下,更简洁;列表可能会占有过多的内存,而有
时候我们想逐个的取值,再者如上例所示斐波那契数列是无穷大的,使用列表不合
适;
3 生成器

生成器是一种使用普通函数语法定义的迭代,当Python 函数不用return 返回值,用yield关键字的时候,函数的返回值为生成器对象;生成器不是使用return返回一个值,而是可以生成多个值,每次一个。

创建生成器
def fun():
    yield 10
    print("z~~~~~~~")
    yield 20

a = fun()
print(a)
print(a.__next__())
print(next(a))
# print(next(a))                      #报错,没有可供返回的值了

#输出

10
z~~~~~~~
20

另一种创建形式,生成器表达式

g = ((i + 2) ** 2 for i in range(2, 27))
print(next(g))
print(next(g))
#输出
16
25

你可能感兴趣的:(python基础教程(知识点提取))