python编程基础——类(基础)

概念

类提供了一种组合数据和功能的方法。创建一个新类意味着创建一个新 类型 的对象,从而允许创建一个该类型的新 实例 。每个类的实例可以拥有保存自己状态的属性。一个类的实例也可以有改变自己状态的(定义在类中的)方法。(引用)


当然,不太理解也没有关系,有些东西积累到一定程度的时候,自认而然就懂了。下面的内容硬着头皮看完,再查查资料或者给博主留言一点点去深入理解。

作用域、命名空间

命名空间

命名空间(namespace) 是一个从名字到对象的映射。
在python解释器执行时会在内存中开辟一个空间,每当遇到一个变量时,就把变量名和对象的键值(内存地址)的对应关系记录下来(大部分命名空间当前都由 Python 字典实现)。当遇到函数定义的时候,只会象征性的把函数名和其对应的内存地址记录下来,至于这个函数是做什么的,里面有多少个变量并不关心。当要执行这个函数的时候,python则会另开辟一块内存来存放函数,这时候函数里面的变量才会存储在新开辟的内存中。这段内存在函数执行完毕后,这块内存就会被回收。因此命名空间也分为一下三种:

  • 全局命名空间:随python解释器执行的顺序,依次加载会持续到解释器退出。
  • 局部命名空间:随函数被调用时加载,随函数调用完后释放。
  • 内置命名空间 :Python 解释器启动时创建的,也会持续到解释器退出。

为了更好的描述,这里举几个例子:
我们可以看到对于全局命名空间,代码顺序执行,在执行到print(id(func))时已经加载函数func()了,所以能够打印出func()id。但执行到print(id(add))时,则运行报错,因为此时还没有加载到函数add()。同理,对于变量也适用。

# 函数
def func():
    pass
print(id(func))
print(id(add))
def add(a,b):
    pass
# 输出结果
1921983168992
NameError: name 'add' is not defined

# 变量
x='测试'
print(id(x))
print(id(y))
y='asd'
#输出结果
1722325771168
NameError: name 'y' is not defined

对于局部命名空间只有在函数,变量被调用时才会存在,如果没有被调用,是不可以的。因此当add()运行时,第一个print()函数会将局部命名空间的id打印出来,但第二个则不会有结果,因为第二个print()在运行时,其要被打印的名称id已经被销毁。

def add():
    def A():
        pass
    def B():
        pass
    a='asd'
    print(id(A),id(B),id(a)) #第一个
add()
print(id(A),id(B),id(a)) # 第二个

对于内置命名空间,解释器启动时创建的。因此在解释器的生命周期中,不管运行多少次,其id都不会改变。

# 内置命名空间
print(id(1),id(range),id(True))
# 输出结果
1535035536 1534672392 1534647472

作用域

作用域 是一个命名空间可直接访问的 Python 程序的文本区域(就是作用范围)。 这里的 “可直接访问” 意味着对变量名的非限定引用会尝试在命名空间中查找。按照作用域的范围,可以分为全局作用域和局部作用域。

  • 全局作用域包含内置名称空间、全局名称空间、在整个文本域的任意位置都能被引用,全局有效。
  • 局部作用域局部名称空间,只能在局部范围生效。
  • 注意 if-elif-elsefor-elsewhiletry-except\try-finally等关键字的语句块中并不会产成作用域。
    那么Python是如何检索名称的呢?Python会从当前的作用域开始搜索,如果没有则转向上层搜索,如果最外层全局变量也没有,则转向内置的命名空间。
    下面让我们看个例子了解一下:
def func():
    a='第一层'
    b='第一层'
    c='第一层'
    def A():
        a='第二层'
        b='第二层'
        for i in range(1):
            a='第三层'
            print(a,b,c,d)
    A()
d='最外层'
func()
# 运行结果
第三层 第二层 第一层 最外层

global和nonlocal

如果在作用域中想使用上一层的变量或者全局变量怎么办呢?这时候就要用到global和nonlocal了。

  • global 无论是在哪个局部作用域中使用,都指代这个变量是全局变量来使用。
  • nonlocal 指该变量为上一层变量。

让我们看两个例子;

扫描二维码关注公众号,回复: 8486820 查看本文章
# 指全局变量
def func():
    a='第一层'
    def A():
        global a
        a='第二层'
    print(a)
    A()
    print(a)
a='最外层'
func()
print(a)
# 运行结果
第一层
第一层
第二层

# nonlocal声明的变量,指该变量为上一层变量。
def func():
    a='第一层'
    def A():
        nonlocal a
        a='第二层'
    print(a)
    A()
    print(a)
a='最外层'
func()
print(a)
#输出结果
第一层
第二层
最外层

上面两段代码,仅改变了函数A()中的变量a的声明方式。细细品味,你会明白global和nonlocal的区别的。




类(基础)

语法结构

简单的类定义起来是这样的:

class ClassName:
	<statement-1>
	```
	<statement-2>

类定义与函数定义一样,必须执行才会起作用。当一个类创建时,也将创建一个新的命名空间,并将其用作局部作用域。因此所有对局部变量的复制都是在这个新命名空间之内。
类的名称一般用大写字母开头,如果名称是两个单词,那么两个单词的首字母都要大写,例如ClassName,这种命名方法叫做“驼峰式命名”。



类对象

当python解释器正常扫描完类定义时,会创建一个类对象。我们可以通过下面这个例子来证明,我们在类用print(),可以发现,python解释器在扫描类后,自动创建了包含整个类定义的类对象(此时print函数被调用),并将其绑定到类定义开头所给出的名称Myclass上。

class Myclass:
    '''自定义类'''
    a='asd'
    print(a)
# 运行结果
asd

属性引用

我们可以通过obj.name的方式来实现对类中名称的引用,我们称这种引用叫属性引用。属性名可以是所有在类对象被创建时存在于类命名空间中的所有名称(包括变量名和函数名)。关于属性的私有化参见类(高级)
例如对于下面这个类:

class Myclass:
    '''类的属性引用'''
    a='asd'
    def func(self):
        return 'hello'
print(Myclass.a,Myclass.func)
print(Myclass.__doc__)

# 运行结果
asd <function Myclass.func at 0x000001D9EDC3E378>
类的属性引用

我们可以看到,通过使用属性引用Myclass.aMyclass.func,返回的是变量a的值和func函数对象。也可以使用Myclass.__doc__来查看该类的类文档。
有意思的是我们可以通过赋值来实现对类的更改。甚至将函数名称所绑定的函数对象更改为整数。

class Myclass:
    '''类的属性引用'''
    a='asd'
    def func(self):
        return 'hello'
def add(x,y):
    return x+y
Myclass.func=233 #将func重新绑定到int对象。
print(Myclass.func)
Myclass.a=5 #更改类中的变量
Myclass.__doc__="hello" #更改类文档
Myclass.func=add 将func重新绑定到add函数对象。
print(Myclass.a,Myclass.func)
print(Myclass.__doc__)

#运行结果
233
5 <function add at 0x000001A2CBB9C1E0>
hello

实例化

类的实例化(“调用”类对象)通过函数表示法来实现的。可以把类对象视为返回该类的一个新实例。对于上述的Myclass,我们可以通过下面的方法来实例化。

x = Myclass()

我们可以使用__init__()方法来实现对类的特定初始化。当一个类定义了__init__()方法时,类的实例化操作会自动为新创建的类实例发起调用__init__()

class Complex:
    def __init__(self,real,imag):
        self.r=real
        self.i=imag
x=Complex(3,4)
print(x.r,x.i)
# 运行结果
3 4


实例对象

我们可以通过属性引用来实现对实例对象的操作。有两种有效的属性名称——数据属性和方法。

  • 数据属性
    数据属性的使用不需要声明,像局部变量一样,在实例化过程中第一次被赋值时产生。
  • 方法
    方法是“从属于”对象的函数。值得注意的是,实例对象的有效方法名称依赖于其所属的类。一个类中所有函数对象的属性都是定义了其实例的相应方法。在下面示例中,x.f是方法引用,MyClass.f是一个函数。x.f本质上是MyClass.f的方法绑定。
class MyClass:
    """一个简单的类"""
    i = 123
    def f(self):
        print("hello world")

x=MyClass()
print(x.f,MyClass.f)
# 输出结果
<bound method MyClass.f of <__main__.MyClass object at 0x000001A2932BEDD8>> 
<function MyClass.f at 0x000001A2935AE378>


方法对象

方法在绑定后,就可以被调用了。

x=MyClass()
x.f()
# 输出结果
hello world

信心的你应该注意到,MyClass.f函数是有参数的,即使参数实际未被使用,但为什么我们使用x.f()调用时却没有输出参数就直接可以运行呢?我们可以看到如果直接运行```MyClass.f`()``是会报错的。

MyClass.f()
# 输出结果
TypeError: f() missing 1 required positional argument: 'self'

方法的特殊之处就在于实例对象会被作为函数的第一个参数被传入。调用x.f()就相当于MyClass.f(x)。简言之,调用一个具有 n 个参数的方法就相当于调用再多一个参数的对应函数,这个参数值为方法所属实例对象,位置在其他参数之前。
为什么要这样做呢?这是为了让解释器知道,但前是哪个类实例中。self就是一个实例(准确来说是实例的引用变量),self就是起到了这个作用——接收实例化过程中传入的所有数据。


类和实例变量

实例变量用于每个实例的唯一数据,而类变量用于类的所有实例共享的属性和方法。

class Cat:
    kind='cat'
    def __init__(self,name):
        self.name=name
C=Cat('FFF')
D=Cat('SSS')
print(C.kind,C.name)
print(D.kind,D.name)
# 输出结果
cat FFF
cat SSS

值得注意的是,在类中使用可变类型需要谨慎,因为类是共享的,如果在数据属性中使用了可变对象,则会导致该该数据熟悉共享,因此正确的使用是将其放在函数中。

class Cat:
    tricks=[]
    def __init__(self,name,trick):
        self.name=name
        self.tricks.append(trick)
C=Cat('FFF','asd')
print(C.tricks,C.name)
D=Cat('SSS','qwe')
print(D.tricks,D.name)
# 输出结果
['asd'] FFF
['asd', 'qwe'] SSS

正确的使用方法是:

class Cat:
    def __init__(self,name):
        self.name=name
        self.tricks=[] #为每个实例创建一个新列表
    def add_tricks(self,trick):
        self.tricks.append(trick)
C=Cat('FFF')
C.add_tricks('asd')
print(C.tricks,C.name)
D=Cat('SSS')
D.add_tricks('qwe')
print(D.tricks,D.name)
#运行结果
['asd'] FFF
['qwe'] SSS


类和实例属性

对于类中定义的属性,我们在实例化后,通过实例化对象也是可以使用的。但是,反之类是不能使用实例属性的。因为是先有类再有的实例化。详细原理可以参见__init__

class ClassName:
    age=7
    def __init__(self):
        self.name=5
c=ClassName()
print(c.age)
print(ClassName.name)
#输出结果
7
type object 'ClassName' has no attribute 'name'

所以,如果我们在实例化的初始函数中定义了同名变量,则会同名覆盖类属性。但删掉实例属性后,我们还是可以通过实例属性的方式访问类熟悉。

class ClassName:
    age=7
    def __init__(self):
        self.age=8
c=ClassName()
print(c.age) #此时输出的是实例化后同名覆盖的实例属性
del c.age #删除实例属性
print(c.age)#此时输出的是类属性
发布了19 篇原创文章 · 获赞 19 · 访问量 3612

猜你喜欢

转载自blog.csdn.net/qq_36733722/article/details/102024692
今日推荐