Python基础+刷廖雪峰教程笔记(三)——main、作用域、面向对象、访问限制、继承多态、属性、__slot__、@property

由于在深度学习的路上,发现自己两年前学习的python有些遗忘,在面向对象这一块尤其不熟悉,故刷一遍廖雪峰老师的官方教程,梳理一下遗漏的知识点。

参考网址:https://www.liaoxuefeng.com/wiki/1016959663602400

1.使用模块

if name==“main”:

test()

如果直接运行xxx.py文件,即主文件,则if通过,并运行test;

若import xxx as x,注意无.py标识,即引用文件,它不是主文件,if不通过,且不运行test()。

文件中加入了main判断语句,则该.py文件只能直接运行、或作为主文件调用其他无main的文件,无法被调用。

demo1:

' a test module '#任何模块代码的第一个字符串都被视为模块的文档注释

__author__ = 'Michael Liao'#使用__author__变量把作者写进去

import sys#导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能

def test():
    args = sys.argv
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':
    test()
#当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,
#if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码

out1:

Hello, world!

2.作用域

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。

正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等;

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量。

类似_xxx和__xxx(双下划线)这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;

demo2:

def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)

我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

3.面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

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

面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。

所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。

面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

数据封装、继承和多态是面向对象的三大特点。

demo2:(from 评论区大佬,帮助理解)

上帝

class 类 (人) instance 实例 (你,我,他) 你会有些属性(身高,年龄,体重) 你会有些技能(吃饭,泡妞)

init 方法的主要作用,就是初始化你的属性,这些属性,在上帝初始化你的时候就要赋予给你,比如zhangsan = Person(170,29,50)这时上帝就把你创造出来了,也就是实例化了你,然后,你到底有哪些技能呢,这就看有没有在类里面定义了,如果有定义泡妞的技能,那么你就可以调用泡妞的技能来泡妞,大致就是这样吧,看看下面的例子就更清楚了。

class Person(object):
# 这里就是初始化你将要创建的实例的属性
    def __init__(self,hight,weight,age):
        self.hight = hight
        self.weight = weight
        self.age = age

# 定义你将要创建的实例所有用的技能
    def paoniu(self):
        print('你拥有泡妞的技能')

    def eat(self):
        print('you can eat')

# 开始创建实例
zhangsan=Person(170,50,29)
lisi = Person(175,100,30)

# 你的实例开始使用它的技能
zhangsan.paoniu()
lisi.eat()

4.类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

demo3:

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

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。
通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去
'''
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score


'''
到__init__方法的第一个参数永远是self,表示创建的实例本身,
因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身
'''

'''
有了__init__方法,在创建实例的时候,就不能传入空的参数了,
必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:
'''

bart = Student('Bart Simpson', 59)
print(bart.name, bart.score)

out3:

Bart Simpson 59

5.数据封装

既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法

demo4:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):#定义了一个方法,第一个参数是self,其他与普通函数无异
        print('%s: %s' % (self.name, self.score))
    def get_grade(self):#封装的另一个好处是可以给Student类增加新的方法
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

lisa = Student('Lisa', 99)#要先创建实例
bart = Student('Bart', 59)
#调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())

#和静态语言不同,Python允许对实例变量绑定任何数据,
#对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同
bart.age = 8
print(bart.age)#不同于类的模板,后来给bart加的age属性
print(lisa.age)#会报错,因为lisa没有age属性

out4:

Lisa A
Bart C
8
Traceback (most recent call last):
  File "C:/Users/xxx/Desktop/hello.py", line 26, in <module>
    print(lisa.age)#会报错,因为lisa没有age属性
AttributeError: 'Student' object has no attribute 'age'

6.访问限制

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

demo5:

class Student(object):

    def __init__(self, name, score):
        self.__name = name#加了__,内部属性不被外部访问
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

bart = Student('Bart Simpson', 59)
bart.__name#无法从外部访问

out5:

Traceback (most recent call last):
  File "C:/Users/xxx/Desktop/hello.py", line 11, in <module>
    bart.__name#无法从外部访问
AttributeError: 'Student' object has no attribute '__name'

如果外部代码要获取name和score,可以给Student类增加get_name和get_score这样的方法。

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

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

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

但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

demo6:

class Student(object):

    def __init__(self, name, score):
        self.__name = name#加了__,内部属性不被外部访问
        self.__score = score

    def get_name(self):#允许获取name
        return self.__name

    def get_score(self):#允许获取score
        return self.__score

    def set_score(self, score):#允许外部代码修改score
        if 0 <= score <= 100:#在方法中,可以做参数检查,防止传入无效参数
            self.__score = score
        else:
            raise ValueError('bad score')

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

bart = Student('Bart Simpson', 59)

print(bart.get_name())#获取私有变量name
print(bart.get_score())#获取私有变量score

#修改实例bart的score为99
bart.set_score(99)
print(bart.get_score())

#Python解释器对外把__name变量改成了_Student__name,
#所以,仍然可以通过_Student__name来访问__name变量
print(bart._Student__name)#不建议这样干,不同版本的Python解释器可能会把__name改成不同的变量名。

#错误写法
bart.__name = 'New Name' # 设置__name变量!
print(bart.__name)
print(bart.get_name())#其实未修改

'''
表面上看,外部代码“成功”地设置了__name变量,
但实际上这个__name变量和class内部的__name变量不是一个变量!
内部的__name变量已经被Python解释器自动改成了_Student__name,
而外部代码给bart新增了一个__name变量
'''

out6:

Bart Simpson
59
99
Bart Simpson
New Name
Bart Simpson

7.继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

继承最大的好处是子类获得了父类的全部功能

demo7:

#编写了一个名为Animal的class,有一个run()方法可以直接打印
class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):#直接从Animal类继承
    pass

class Cat(Animal):
    pass

#由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法
dog = Dog()
dog.run()

cat = Cat()
cat.run()

out7:

Animal is running...
Animal is running...

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

demo8:

#编写了一个名为Animal的class,有一个run()方法可以直接打印
class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):#直接从Animal类继承
    def run(self):#也可以对子类增加一些方法
        print('Dog is running...')

    def eat(self):
        print('Eating meat...')

class Cat(Animal):
    def run(self):
        print('Cat is running...')

dog = Dog()
dog.run()

cat = Cat()
cat.run()

#数据类型探讨
b = Animal()
print(isinstance(b, Animal))
print(isinstance(dog, Dog))
print(isinstance(dog, Animal))

out8:

Dog is running...
Cat is running...

True
True
True

当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样。

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

demo9:

#编写了一个名为Animal的class,有一个run()方法可以直接打印
class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):#直接从Animal类继承
    def run(self):#也可以对子类增加一些方法
        print('Dog is running...')

class Cat(Animal):
    def run(self):
        print('Cat is running...')


def run_twice(animal):
    animal.run()
    animal.run()

#传入的是Animal的实例,Animal()表示创建了一个实例
run_twice(Animal())
#传入的是Dog的实例
run_twice(Dog())
#传入的是Cat的实例
run_twice(Cat())

#再定义一个Tortoise类型,也从Animal派生
class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly...')

run_twice(Tortoise())

out9:

Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...
Tortoise is running slowly...
Tortoise is running slowly...

新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

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

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

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。

总结:(from 评论区大佬 id:黑漆漆的四壁 )

demo10:

class Animal(object):   #编写Animal类
    def run(self):
        print("Animal is running...")

class Dog(Animal):  #Dog类继承Amimal类,没有run方法
    pass

class Cat(Animal):  #Cat类继承Animal类,有自己的run方法
    def run(self):
        print('Cat is running...')
    pass

class Car(object):  #Car类不继承,有自己的run方法
    def run(self):
        print('Car is running...')

class Stone(object):  #Stone类不继承,也没有run方法
    pass

def run_twice(animal):
    animal.run()
    animal.run()

run_twice(Animal())
run_twice(Dog())
run_twice(Cat())
run_twice(Car())
run_twice(Stone())

out10:

Animal is running...
Animal is running...
Animal is running...
Animal is running...
Cat is running...
Cat is running...
Car is running...
Car is running...

AttributeError: 'Stone' object has no attribute 'run'

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

8.获取对象类型

demo11:

class MyObject(object):
    def __init__(self):
        self.x = 9
    def power(self):
        return self.x * self.x

obj = MyObject()
#测试该对象的属性
print(hasattr(obj, 'x')) # 有属性'x'吗?
print(obj.x)
print(hasattr(obj, 'y')) # 有属性'y'吗?
print(setattr(obj, 'y', 19)) # 设置一个属性'y'
print(hasattr(obj, 'y'))# 有属性'y'吗?
print(getattr(obj, 'y')) # 获取属性'y'
print(obj.y) # 获取属性'y'
print(getattr(obj, 'z', 404)) # 获取属性'z',如果不存在,返回默认值404
#也可以获得对象的方法
print(hasattr(obj, 'power')) # 有属性'power'吗?

print(getattr(obj, 'power')) # 获取属性'power'
fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
print(fn) # fn指向obj.power

print(fn()) # 调用fn()与调用obj.power()是一样的

out11:

True
9
False
None
True
19
19
404
True
<bound method MyObject.power of <__main__.MyObject object at 0x000001AAB0EB3710>>
<bound method MyObject.power of <__main__.MyObject object at 0x000001AAB0EB3710>>
81

demo12:

#判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple
>>> isinstance([1, 2, 3], (list, tuple))
True

通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。

9.实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性。

给实例绑定属性的方法是通过实例变量,或者通过self变量。

如果Student类本身需要绑定一个属性呢,可以直接在class中定义属性,这种属性是类属性,归Student类所有。

demo13:

class Student(object):
    name = 'Student'

s = Student() # 创建实例s
print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
print(Student.name) # 打印类的name属性
s.name = 'Michael' # 给实例绑定name属性
print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
del s.name # 如果删除实例的name属性
print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了


out13:

Student
Student
Michael
Student
Student

从上面的例子可以看出,在编写程序的时候,千万不要对实例属性类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

10.使用__slot__

demo14:

#创建一个class的实例
class Student(object):
    pass

#给实例绑定一个属性
s = Student()
s.name = 'Dean' #动态给实例绑定一个属性
print(s.name)

#给实例绑定一个方法
def set_age(self, age):
    self.age = age

from types import MethodType
s.set_age = MethodType(set_age, s)#给实例绑定一个方法
s.set_age(25)#调用实例方法
print(s.age)
#给一个实例绑定的方法,对另一个实例不起作用

#为了给所有实例都绑定方法,可以给class绑定方法
def set_score(self, score):
    self.score = score

Student.set_score = set_score#给class绑定方法
#这样所有实例均可调用
s.set_score(100)
print(s.score)
s2 = Student()
s2.set_score(99)
print(s2.score)

out14:

Dean
25
100
99

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。

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

demo15:

#创建一个class的实例
class Student(object):
    __slots__ = ('name', 'age') #用tuple定义允许绑定的属性名称

s = Student() #创建新的实例
s.name = 'Dean' #绑定属性name
s.age = 25 #绑定属性age
#s.score = 99 #绑定属性score,会报错


class GraduateStudent(Student):
    pass

g = GraduateStudent()
g.score = 9999

注意:

由于’score’没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

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

除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。

有趣的是,如果子类继承自一个没有__slots__的父类,那么子类的__slots__限制不起作用。

11.使用@property

既能检查参数,又可以用类似属性这样简单的方式来访问类的变量。

Python内置的@property装饰器就是负责把一个方法变成属性调用的。

demo16:

class Student(object):

    @property#把一个getter方法变成属性,只需要加上@property
    def score(self):
        return self._score

    @score.setter#@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

s = Student()
s.score = 60# OK,实际转化为s.set_score(60)
print(s.score)# OK,实际转化为s.get_score()

out16:

60

demo17:

#只定义getter方法,不定义setter方法就是一个只读属性
class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth

#面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来

要特别注意:属性的方法名不要和实例变量重名。

如果重名了,就无法确定是调用方法还是属性。

错误代码示意:

class Student(object):

    # 方法名称和实例变量均为birth:
    @property
    def birth(self):
        return self.birth

正常情况,我们调用类方法时,通常这样操作:实例 = 类.方法()。

但是作为一个完美的pythoner觉得这样太麻烦了:python不应该那么复杂

于是他想出了一个方法,把实例调用类方法的操作改成,实例调用类属性的方式:实例.属性 = 值

于是这个(@property)加这个(@xx_setter)就站着实现了这一步操作。

此时,如果你要调用某个方法的时候,就可以按照实例调用类属性的方式:实例.方法 = 值。

但是如果,你的方法里面,有个属性的名字和方法名是一样的。因为编译时无法确定调用的时方法还是属性,导致无限递归,直至内存溢出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值