AutoCV第四课:Python基础

Python基础

注意事项

一、2023/4/4更新

经杜老师纠正,生成器只是一个技术,不要强制说生成器可以节省内存空间(节省内存low b说法,很掉价🤣)。下面是杜老师的解释:

生成器,是一种《设计模式》的实践,它与内存无关。

即使不使用生成器,正常的程序开发也不会出现傻乎乎的产生全部数据再处理。

1.生成器,代表的是一种范式,一种规范的实践

2.生成器,还有send语法,可以实现协程的作用

可参考协程与异步IO

这里同时扩展了《设计模式》这件事,它引导着OOP(面向对象编程)的开发进程。指导了很多新时代语言,它是软件开发和计算机基础很重要的内容。设计模式(比如之前yolo.cu中的接口模式就是一种设计模式),相当于一种思想,指导如何写程序,设计结构。

不同的语言(python、c++、java)都只是不同的语言规范,实现相通的思想,那就是设计模式或者编程范式。

设计模式可参考设计模式|菜鸟教程

前言

手写AI推出的全新保姆级从零手写自动驾驶CV课程,链接。记录下个人学习笔记,仅供自己参考。
本次课程主要学习类及其基本概念以及生成器、迭代器和装饰器的相关知识
课程大纲可看下面的思维导图。

在这里插入图片描述

1.class

1.1 基本介绍

Python中的类(即class)是一种面向对象的编程工具,可以用来描述和创建对象的模板。类中定义了对象的属性和行为,可以实现封装、继承和多态等面向对象编程的特性。(from chatGPT)

类的语法结构如下:

class ClassName:
    '''类的帮助文档字符串'''
    
    # 类属性,是类的所有实例共有的属性
    class_attribute = value
    
    def __init__(self, arg1, arg2, ...):
        '''初始化方法,创建实例时被调用'''
        # 实例属性,每个类都有自己的属性
        self.instance_attribute = value
        # 调用父类初始化方法
        super().__init__(arg1, arg2, ...)
       
    def instance_method(self, arg1, arg2, ...):
        '''实例方法,操作实例的属性'''
        pass
    
    @classmethod
    def class_method(cls, arg1, arg2, ...):
        '''类方法,操作类的属性'''
        pass
    
    @staticmethod
    def static_method(arg1, arg2, ...):
        '''静态方法,不涉及实例和类,一般用来实现工具函数'''
        pass

其中,

  • 类属性是类的所有实例共有的属性,通过类名可以直接访问(比如Animal.life)
  • 实例属性是每个实例有自己的属性,通过self关键字访问(比如dog.name)
  • 实例方法是操作实例的方法,必须有self作为第一个参数(比如dog.eat())
  • 类方法是操作类的方法,必须有cls作为第一个参数,可以通过类名或实例名调用
  • 静态方法是不涉及实例和类的方法,可以通过类名或实例名调用

类的继承通过在类定义时在括号内指定父类,如:

class SubClass(ParentClass):
    '''子类定义'''

子类可以继承父类的属性和方法,并可以在子类中添加新的属性和方法,实现代码的复用和扩展。

1.2 类的实例化

实例化是指,根据类创建一个实际的对象。类是抽象的概念,而对象则是具体的实现。比如说类Animal,动物类这是一个抽象的概念,自然界有那么多动物,具体是那种动物呢?可以通过将其实例化赋予抽象类Animal特点,让它变成具体的实现,比如dog,它就是抽象类Animal的一种实例对象。

在Python中,要实例化一个类,需要使用类名加上一对括号,如Animal(),这将创建类的一个实例。实例化类时,会调用类的初始化函数__init__(),并返回一个新的实例。实例化类的一个简单示例如下:

class Animal():

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

    def eat(self):
        print(f"{self.name}正在吃东西.")

dog = Animal("旺财")  # 类的实例化 
print(dog.name)		 # 输出: 旺财
dog.eat()			 # 输出:旺财正在吃东西.

在上述示例中,我们定义了一个Animal类,并在其初始化函数中传入一个参数name,在实例方法eat中使用了传入的参数name。然后,我们使用Animal实例化一个对象dog,并传入一个参数"旺财"

实例化类时,如果类没有定义__init__()构造函数,则会使用默认构造函数。

拓展之self参数:在Python中,self是一个指向类实例本身的引用。类中的实例方法需要访问该实例,因此在定义实例方法时必须将self作为第一个参数,以便在内部使用它。self类似于C++中的this指针,self实际上是一个约定俗成的参数名称,它可以被替换为其它名称。

1.3 魔法函数

在Python中,有一些特殊的函数被称为魔法函数(magic method),也叫特殊方法。这些方法以双下划线__开头和结尾,在类中重写这些方法可以实现一些特殊的行为,比如操作符重载、自定义对象转换等。(from chatGPT)

下面是类中几个常用的魔法方法:

  • __init__(self, ...):构造函数(也叫初始化函数),在创建对象时自动调用,用于初始化对象的属性
  • __call__(self, ...):使对象可调用,当对象被当作函数如obj(...)调用时,该方法会被触发
  • __getitem__(self, key):获取特定索引值的元素,通过obj[key]调用
  • __iter__(self):该方法被调用时返回了一个实现了__next__()方法的迭代器对象
    • 简单解释下迭代器是一个可以被迭代的对象,它能够在每次调用__next__()方法时返回其下一个值,直到所有值都被遍历完毕。
    • 明确下列表、元组、字符串都是可迭代对象,但不是迭代器,它们本身不含有__next__方法,所以无法通过next()函数来进行迭代,但是它们可以通过内置函数iter()转换为迭代器
    • 如果一个类实现了魔法方法__iter__()__next__(),那么它就可以被视为一个迭代器
  • __next__(self):返回当前迭代器的下一个值,如果没有下一个值,则会抛出StopIteration异常

简单的示例代码如下所示:

# __init__魔法方法
class Person:
    def __init__(self, name) -> None:
        self.name  = name

person = Person("Tom")
print(person.name) # 输出:Tom

# __call__魔法方法
class Add:
    def __init__(self, num) -> None:
        self.num = num
    
    def __call__(self, x):
        return self.num + x

add = Add(5)
print(add(3)) # 输出:8

# __getitem__魔法方法
class MyList:
    def __init__(self, lst):
        self.lst = lst

    def __getitem__(self, index):
        return self.lst[index]

mylist = MyList([1, 2, 3])
print(mylist[1]) # 输出:2

# __iter__和__next__魔法方法
class Reverse:
    def __init__(self, data) -> None:
        self.data = data
        self.index = len(data)
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

rev = Reverse("hello")
for char in rev:
    print(char) # 输出:o l l e h

2.生成器

2.1 定义

Python中,生成器是一种特殊的迭代器(定义,本质上是迭代器),它可以通过生成器函数或生成器表达式来创建(创建方法)。生成器可以用于生成协程,通过send()方法进行协程间的通信,实现异步编程和协作式多任务处理(用途)。

2.2 生成器函数

生成器函数是一种特殊的函数,它使用yield语句来生成一个序列。当生成器函数被调用时,它返回一个生成器对象,可以通过该对象来迭代生成器中的元素。生成器可以用做懒加载,即只有在使用的生成器元素时才会生成该元素,而不是提前生成全部元素。

下面是一个简单的示例代码,演示如何使用生成器函数来创建一个生成器:

# 生成器函数
def my_generator(max):
    current = 0
    while current < max:
        yield current
        current += 1

# 创建生成器
gen = my_generator(5)
for i in gen:
    print(i) # 输出:0 1 2 3 4

在这个示例代码中,首先调用my_generator函数来创建一个生成器对象gen。然后,我们使用for循环来迭代gen中的元素,并输出每个元素的值。在迭代过程中,Python会自动调用生成器对象的__next__方法来获取下一个元素的值,直到生成器中的所有元素都被迭代完毕。

值得注意的是:当执行到yield语句时,函数会暂停执行,并返回当前的值,同时将函数的状态保存下来。当下一次调用生成器对象的__next__方法,函数会从上一次暂停的位置继续执行,直到遇到下一个yield语句或函数结束。

2.3 生成器表达式

生成器表达式类似于列表推导式,但使用圆括号而不是方括号来表示。与列表推导式不同的是,生成器表达式并不立即生成一个列表,而是生成一个生成器对象。

下面是一个简单的示例代码,演示如何使用生成器表达式来创建一个生成器:

gen = (x for x in range(5))
for i in gen:
    print(i) # 输出:0 1 2 3 4

和生成器函数一样,我们也可以使用生成器表达式来实现更复杂的逻辑和条件判断,例如,筛选一个列表中的偶数元素,代码如下:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
gen = (x for x in my_list if x % 2 == 0)
for i in gen:
    print(i) # 输出:2 4 6 8 10

3.迭代器和可迭代对象

3.1 迭代器

在Python中,迭代器是一种可以遍历容器中元素的对象,它可以逐个返回容器中的元素,并在遍历过程中记录当前位置。迭代器可以用于处理大量数据或无法一次加载到内存中的数据集(Dataset)。

在Python中,迭代器必须实现两个方法:__iter____next__。其中__iter__方法返回迭代器对象本身,而__next__方法返回容器中的下一个元素,如果容器中没有更多元素,则抛出StopIteration异常。

下面是一个迭代器的示例:

class MyIterator:
    def __init__(self, data):
        self.index = 0
        self.data = data

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        result = self.data[self.index]
        self.index += 1
        return result

my_list = [1, 2, 3, 4]
my_iterator = MyIterator(my_list)

for item in my_iterator:
    print(item) # 输出:1 2 3 4

在上面的代码中,MyIterator类实现了__iter____next__方法,使得该类的实例my_iterator成为一个迭代器对象。在for循环中,我们使用my_iterator来进行迭代,最终输出列表中的元素。注意,在迭代器中,我们呢需要手动实现__next__方法,每次迭代都需要更新迭代器的状态。

3.2 可迭代对象

在Python中,可迭代对象是指实现了__iter__()方法的对象,例如列表、元组、字符串等,它们可以被for循环遍历。

下面是可迭代对象的一个简单示例:

class MyIterable:
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        return iter(self.data)

my_list = [1, 2, 3, 4]
my_iterable = MyIterable(my_list)

for item in my_iterable:
    print(item) # 输出:1 2 3 4

上面的代码中,MyIterable类实现了__iter__方法,该方法通过调用Python内置函数iter()返回了一个迭代器对象。在MyIterable类的实例化中,我们将一个列表传递给了MyIterable类,使得该类的实例my_iterable成为一个可迭代对象。在for循环中,我们使用my_iterable来进行迭代,最终输出列表中的元素。

3.3 区别和联系

区别:可迭代对象只是一个具有__iter__()方法的对象,而迭代器则是实现了__iter__()方法和__next__()方法的对象。可迭代对象可以被用于for循环中,但是如果我们需要使用next()函数来逐一获取其元素,那么就需要将其转换成迭代器。

联系:可迭代对象可以通过实现__iter__()方法来返回一个迭代器,迭代器可以用来遍历可迭代对象。另外,可迭代对象和迭代器都可以通过for循环来遍历元素。

下面是一个简单的示例,演示了如何将可迭代对象转换成迭代器并调用next()方法进行遍历:

my_list = [1, 2, 3, 4, 5]
# 将列表对象转换为一个迭代对象
my_iter = iter(my_list)

while True:
    try:
        # 获取下一个元素的值
        value = next(my_iter)
        print(value) # 输出:1 2 3 4 5
    except StopIteration:
        break

4.装饰器

4.1 定义和语法

Python的装饰器本质上是一个函数,它接受一个函数对象作为参数,并返回一个修改后的函数对象(参数和返回值均为函数对象)。装饰器通常使用@decorator的语法糖来使用,它可以将装饰器应用于函数或类的定义之前,从而实现对其功能的增强或修改。(from chatGPT)

下面是一个简单的示例,演示了如何使用装饰器来打印函数的运行时间:

import time

def time_it(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {(end_time - start_time) * 1000 :.3f} ms.")
        return result
    return wrapper

@time_it # 等价于 my_func = time_it(my_func)
def my_func():
    time.sleep(1)

my_func()

在上述代码中,我们定义了一个名为time_it装饰器函数,它接受一个函数对象作为参数,并返回一个修改后的函数对象。接着,我们使用@time_it的语法糖将装饰器应用于函数my_func的定义之前,这意味着函数my_func将被传递给装饰器函数time_it,并被修改为一个新的函数对象。当调用函数my_func时,实际上调用的是被修改后的函数对象wrapper,它会记录函数运行时间,并输出运行时间信息。

装饰器的语法结构如下:

@表达式
	def 被修饰的函数

其中,表达式需要返回一个函数对象,这个函数对象就是用来修饰函数的。以上面的代码为例,这里的表达式就是time_it,被修饰的函数就是my_func。上述装饰器也可以写成函数名 = 表达式(函数名)的形式即my_func = time_it(my_func),可实现相同的功能。

4.2 类作为装饰器

上面实现了函数作为装饰器的用法,现在我们来看看类作为装饰器的几种形式。

4.2.1 形式1

使用class的__init__作为装饰入口,传递my_func;使用class的__call__作为调用入口

示例代码如下:

import time

class TimeIt():
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        start_time = time.time()
        result = self.func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {self.func.__name__} took {(end_time - start_time) * 1000 :.3f} ms.")
        return result

@TimeIt # 相当于 my_func = TimeIt(my_func) 进行实例化
def my_func():
    time.sleep(1)

my_func()   # 相当于调用实例化对象的__call__魔术方法

这里使用类来实现装饰器,__init__方法接受被装饰函数,__call__方法作为装饰器的入口,接受被装饰函数的参数,计算其运行时间并打印。@TimeIt相当于执行了my_func = TimeIt(my_func),将被装饰函数传递给装饰器的__init__方法。

4.2.2 形式2

使用class的__call__作为装饰入口,传递my_func,返回wrapper;在warpper中实现统计耗时

import time

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

    def __call__(self, func):
        def wrapper():
            start_time = time.time()
            result = func()
            end_time = time.time()
            print(f"Function {self.name} took {(end_time - start_time) * 1000 :.3f} ms.")
            return result
        return wrapper

@TimeIt("test") # 相当于 my_func = TimeIt("test")(my_func)
def my_func():
    time.sleep(1)

my_func()

这里实现是将被装饰的函数my_func作为参数传递给了__call__方法,因此,在执行@TimeIt("test")时,实际上是创建了一个TimeIt对象,并将"test"作为参数传递给了该对象的__init__初始化方法,然后将my_func作为参数传递给了该对象的__call__方法,最终执行了wrapper函数,并返回了其返回值。

4.3 修饰类的成员函数

下面的示例演示了装饰器修饰类的成员函数:

import time

def time_it(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {(end_time - start_time) * 1000 :.3f} ms.")
        return result
    return wrapper

class Tool():
    
    @time_it # 相当于my_func = time_it(my_func)
    def my_func():
        time.sleep(1)

Tool.my_func()

上述代码中,@time_it是一个装饰器,用来修饰Tool类的成员函数my_func。通过在my_func前加上@time_it,实现了对my_func的装饰,使得my_func在被调用时,会先调用time_it函数统计函数运行时间。调用Tool.my_func()时,实际上是调用了wrapper函数,并将其结果返回。

4.4 常见装饰器

在Python中有一些常见的内置装饰器,如@classmethod@staticmethod@property,下面我们分别介绍下:(from chatGPT)

  • @classmethod装饰器用于将一个函数定义为类方法,类方法可以在不创建实例的情况下被类本身调用,常用于在类级别上操作或返回类的某些属性或方法。使用@classmethod修饰的函数的第一个参数必须是cls,代表调用该方法的类本身。
  • @staticmethod装饰器用于将一个函数定义为静态方法,静态方法和类方法类似,但是在静态方法中不需要传递类或实例参数,使用@staticmethod修饰的函数没有特殊的参数要求,因为它们与类或实例无关,所以可以在不实例化类的情况下直接调用。
  • @property装饰器用于将一个函数定义为属性,使得该函数可以像类属性一样被访问,但是每次访问该属性时都会动态计算。是使用@property修饰的函数必须有一个返回值,该返回值就是该属性的值。

下面是一个简单的示例,用来说明这几种内置装饰器的功能:

class Myclass:
    def __init__(self, value):
        self._value = value
    
    @classmethod
    def from_string(cls, value_string):
        value = int(value_string)
        return cls(value)  # 等价于 Myclass(value)

    @staticmethod
    def say_hello():
        print("Hello!")
    
    @property
    def value(self):
        return self._value
    
    @value.setter
    def value(self, new_value):
        self._value = new_value

instance = Myclass.from_string("123")
Myclass.say_hello()		# 输出:Hello!
instance.say_hello()	# 输出:Hello!
print(instance.value)	# 输出:123
instance.value = 456
print(instance.value)	# 输出:456

在这个示例中,我们首先使用类方法from_string来创建一个MyClass实例,然后调用静态方法say_hello,最后输出value属性的值,并通过属性赋值方法来修改它的值。对于该示例主要有以下几点说明:

  • 这个示例定义了一个名为MyClass的类,它有一个构造函数__init__,一个类方法from_string,一个静态方法sat_hello和一个属性value
  • from_string方法是一个类方法,它用于根据传入的字符串参数创建一个MyClass的实例。在这个方法内部,我们首先将字符串转换为整数类型,然后使用cls参数来创建一个新的类实例。
  • say_hello方法是一个静态方法,它不需要访问任何实例属性或者类属性,因此它可以作为类方法而不是实例方法来定义。我们可以在不实例化MyClass的情况下,直接调用这个静态方法。
  • value是一个实例属性,我们可以通过@property装饰器将其变成只读属性,即只能通过instance.value来读取,而不能通过instance.value = new_value来修改。如果我们希望value属性是可写的,那么可以再定义一个带有@value.setter装饰器的方法,用于处理新值的赋值操作。

4.5 基于装饰器的分配机制

基于装饰器的分配机制,是指在Python中,装饰器可以用于为类或函数动态地添加额外地属性或方法。通过装饰器,我们可以在不修改原始类或函数定义的情况下,为它们添加新的功能。在Python中,这种机制被称为基于装饰器的分配机制(Decorator-Based Dispatching)

例如,我们可以使用装饰器为一个函数添加日志功能,如下所示:

def log(func):
    def wrapper(*args, **kwargs):
        print("Calling function:", func.__name__)
        resutlt = func(*args, **kwargs)
        print("Function result:", resutlt)
        return resutlt
    return wrapper

@log
def add(a, b):
    return a + b

add(1, 2) # 输出:Calling function: add  Function result: 3

在这个例子中,log装饰接收一个函数作为参数,返回一个新的函数wrapper,当我们调用add函数时,实际上是调用了wrapper函数,从而实现了日志功能的添加。

通过装饰器,我们可以轻松地为类和函数添加新的功能,而不需要修改它们地定义。这样,我们可以保持代码的简洁性和可读性,同时又能够满足不同的需求。

5.作业

5.1 作业7

内容:自定义类Fib(count),通过迭代,每次吐出一个斐波拉契数列数列值,最多count次

可以利用迭代器,在类Fib重写__iter____next__方法,以实现迭代输出斐波那契数列的功能,示例代码如下:

class Fib:
    def __init__(self, count):
        self.count = count
        self.current = 0
        self.next = 1
        self.index = 0

    # 返回迭代器对象
    def __iter__(self):
        return self

    # 获取下一个迭代器元素
    def __next__(self):
        if self.index >= self.count:
            raise StopIteration
        result = self.current
        self.current, self.next = self.next, self.current + self.next
        self.index += 1
        return result

for i, number in enumerate(Fib(10)):
    print(f"第 {i+1:<2} 个数是 {number:<2}")  # :<2表示输出宽度为2,左对齐

输出结果如下:

在这里插入图片描述

拓展之enumerate

enumerate是Python中的一个内置函数,用于枚举一个可迭代对象的元素并返回一个由元素下标和元素本身组成的二元组。其语法格式如下:

enumerate(iterable, start=0)

其中,iterable表示要枚举的可迭代对象,start表示起始下标,默认为0。返回的是一个迭代器对象。enumerate函数在循环中非常有用,可以同时获得下标和元素的值。

下面是一个简单示例:

fruits = ['apple', 'banana', 'orange']
for i, fruit in enumerate(fruits):
    print(i, fruit)	# 输出:0 apple 1 banana 2 orange

5.2 作业8

内容:自定义类装饰器,修饰作业7的类,并修改这个函数的返回值+5

示例代码如下:

class Hook:
    def __init__(self, name):
        self.name = name
    
    def __call__(self, func):
        def warpper(*args, **kwargs):
            result = func(*args, **kwargs)
            return result + 5
        return warpper

class Fib:
    def __init__(self, count):
        self.count = count
        self.current = 0
        self.next = 1
        self.index = 0

    # 返回迭代器对象
    def __iter__(self):
        return self

    # 获取下一个迭代器元素
    @Hook("FibTimer")  # __next__ = Hook(__next__)
    def __next__(self):
        if self.index >= self.count:
            raise StopIteration
        result = self.current
        self.current, self.next = self.next, self.current + self.next
        self.index += 1
        return result

for i, number in enumerate(Fib(10)):
    print(f"第 {i+1:<2} 个数是 {number:<2}")  # :<2表示输出宽度为2,左对齐

输出结果如下:

在这里插入图片描述

总结

本次课程学习的内容挺多的,首先学习了类的基本概念和魔法函数,其次学习了Python中的三大神器,分别是生成器、迭代器和装饰器,了解了构建生成器的两种方式:生成器函数和生成器表达式。了解了迭代器在类中的使用(通过__iter____next__),同时还了解了装饰器的相关语法(包括函数作为装饰器和类作为装饰器)以及python中常见的内置装饰器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱听歌的周童鞋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值