Skip to content

函数 & 类

函数

定义

python
def 函数名(形参列表)
    函数执行语句
    ...

参数类型

Python 弱类型语言,可动态改变变量的类,虽然可通过变量名: 类型指定变量的类型,但是仅用于提示开发者。

默认值

Python 支持为参数指定参数的默认值。有默认值的参数一定得在没有默认值参数的后面。

⚠️

Python 的默认值只会计算一次,即如果默认值是一个可变类型,并且函数内部对其进行了修改,那么多次调用会导致,每一次调用时的默认值都不同。

python
# 每多一次调用,输出的列表就多一个值
def test1(l = []):
    l.append(random.randint(0, 100))
    print(l)

# 不管调用多少次,都只包含一个值
def test2(l = None):
    if l is None:
        l = []
    l.append(random.randint(0, 100))
    print(l)

动态参数列表

Python 使用 * 定义动态参数列表,用于接收形参列表以外的非具名参数,如:

python
def add_all(*args):  # args 是一个元组
    result = 0
    for i in args:
        result += i
    return result
    
print(add_all(1, 2, 3))  # 输出 6

Python 还可以使用 ** 接受非形参列表的命名参数的字典,如:

python
def values(**kwargs):
print(kwargs)


values(a=1, b=2)  # 输出 {'a': 1, 'b': 2}

如果同时存在普通参数和 *args 和 **kwargs,则先定义普通参数、元组动态参数、最后是具名动态参数

特殊参数

Python 默认情况下参数都能通过按位置传或者使用关键字传参。

⚠️

按位置传参需要在使用关键字之前。

  • '/': 在之前的参数只能使用位置传参
  • '*': 在之后的参数只能使用关键字传参

在这个两个符号之间的,就是默认情况。

调用

python
函数名(参数列表)

函数名(参数名=参数值,...)

传入动态参数列表

Python 使用 * 传入动态参数列表,如:

python
def add(a, b):
    return a + b


print(add(*[1, 2]))  # 输出 3
print(add(*(1, 2)))  # 输出 3

Python 还可以使用 ** 传入命名参数的动态参数列表,如:

python
def add(a, b):
    return a + b


print(add(**{'a': 1, 'b': 2}))  # 输出 3

全局变量和局部变量

在函数题内部的变量为局部变量,可通过 global 关键字申明为全局变量。

匿名函数

使用 lambda 关键字定义

匿名函数名 = lambda 形参列表: 执行代码

定义

python
class 类名:
    # 变量
    # 方法

TIP

  • 使用 __ 开头的变量或者方法为类私有,只能在类内部调用(可通过添加 _类名 前缀强行访问)
  • 类方法的形参列表的第一个使用 self 表示类自身的引用
  • __init__ 为类初始化方法

大部分情况下可能会说 __init__ 方法是 Python 类的构造方法,不过我认为 __new__ 叫构造方法更合理。 通过实现 __new__ 实现单例模式(不能使用 @classmethod 的类方法):

python
class Signleton:

    __instance = None

    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls) # object 的 __new__ 不支持其它参数
        return cls__instance

obj1 = Singleton()
obj2 = Singleton()

print(obj1 == obj2)

继承

python
class 子类名(父类名):
    # 变量
    # 方法
  • 默认继承至 object,可忽略不写
  • 子类可通过 super() 调用父类的方法或变量
  • 子类不具有父类的私有成员(初始化方法除外,子类默认继承父类的初始化方法,自定义后,可通过 supper().__init__() 调用父类的初始化方法)

注意

  • Python 支持多继承
    • 如果父类具有相同的变量,Python 会通过 MRO 机制进行处理(先定义的父类,优先级高,使用了super()调用父类的方法,则 Python 会通过 类.__mro__ 属性的顺序执行父类的方法)
  • Python 不支持方法重载

类方法 | 静态方法 | 普通方法(实例方法)

  • 类方法:使用 @classmethod 装饰的方法,推荐使用 cls 作为第一个参数名,在通过类和实例调用时都是传入类的 type 实例,也就是类方法只能修改类顶层定义的变量。
  • 静态方法:使用 @staticmethod 装饰的方法,和 Java 等其它语言的静态方法类类似,不会自动传入任何参数。
  • 普通方法:不使用装饰器,且第一个参数为 self(叫什么名字是什么无所谓) 的方法

为什么普通方法叫什么名无所谓

  1. 首先就算是普通的方法,也能通过 类名.method() 直接调用
  2. 如果存在参数列表,则通过类名调用需要手动传
  3. 普通方法其实是 python 帮忙处理了,在通过实例调用时会自动将实例对象传为第一个参数,这也是一个问题点,如果类里有一个没参数列表的方法,通过通过实例调用则会引发错误,因为 python 会自动传入实例作为第一个参数
实例方法类方法静态方法
定义方法接受一个默认参数
一般命名为 self
使用 @classmethod 装饰器装饰
并接受一个默认参数
一般命名为 cls
使用 @staticmethod 装饰器装饰
不接受默认参数
调用方式使用类实例调用:
obj.methodName(args)
使用类类型调用:
className.methodName(obj, args)
也支持使用类实例和类直接调用:
className.methodName(args)
obj.methodName(args)
和类方法一样的调用方式
权限范围可以范围实例变量:
self.objVar
type(self).classVar
self.classVar
只能访问类变量,类变量是共享的不能访问类变量和实例变量,只能访问方法参数
使用场景需要访问或修改实例的状态需要操作类属性或调用类方法方法逻辑与类和实例无关,但与类相关联

在实例方法中使用 type(self).classVarself.classVar 访问的区别

使用 type(self).classVar 访问使用的是类变量,修改会使得后续实例范围时的值被更改。

同时因为实例变量的优先级访问比类变量优先级高,定义实例变量后,直接访问的就是实例变量。

python
class MyClass:
    count = 1

    def type_count(self):
        # self.count += 1
        type(self).count += 1

    def self_count(self):
        self.count += 1


MyClass().type_count()
print(MyClass.count) # 2
MyClass().type_count()
print(MyClass.count) # 3

cls = MyClass()
cls.self_count() # 4
print(cls.count)
cls.self_count() # 5
print(cls.count)

cls = MyClass()
cls.self_count() # 4
print(cls.count)
cls.self_count() # 5
print(cls.count)

不能使用 @classmethod 的方法

这些方法默认就是类方法,第一个参数为 cls,下面列出类方法,还有一个用在元类中的 __prepare__ 方法

__new__

在上面的类的定义中有提到,通过使用 __new__ 实现单例模式。

__init_subclass__

初始化子类的时候调用,第一个参数也是 cls,表示子类的类型。

__class_getitem__

让类型可直接使用 className[item] 语法进行访问,第一个参数是 cls、第二个参数是传入的 item

元类

Python 的元类(metaclass)用于控制类的创建和属性的访问等。

  • 控制类的创建过程:在元类中实现 __new__ 方法
  • 修改类的属性:
  • 其它操作

元类需要继承至 type 类,通过 metaclass 指定类使用的元类类型

使用元类为所有方法添加日志
python
class LoggerMeta(type):

    @staticmethod
    def __logger_advice(func):

        def wrapper(*args, **kwargs):
            print(f"LoggerMeta: {func.__name__}")
            return func(*args, **kwargs)

        return wrapper

    def __new__(cls, name, bases, attrs):
        for key, attr in attrs.items():
            if callable(attr):
                attrs[key] = cls.__logger_advice(attr)
        return super().__new__(cls, name, bases, attrs)


class MyClass(metaclass=LoggerMeta):

    def test(self):
        print("MyClass.test()")


cls = MyClass()
cls.test()

常见的可重写方法

官方文档:

__str__

重写调用 str() 返回的结果。

默认的 __str__ 实现是调用了 __repr__ 方法

可通过 PyObject_Str 中做到对应实现代码。

序列化为字符串时调用
python
class Person:

    def __init__(self, name, age):
        self.name = name;
        self.age = age;
    
    def __str__(self):
        return f"Person(name = {self.name}, age = {self.age})"

person = Person("张三", 24)
print(f"person = {person}")
print(person)
person_str = str(person)
print(person_str)

__repr__

重写调用 repr() 返回的结果,repr() 用于返回对象的标准字符串。 一般推荐返回能直接通过 eval() 函数直接重新构建相同对象内容的字符串。

修改 repr() 返回的内容
python
class Person:

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

    def __str__(self):
        print("__str__")
        return super().__str__()

    def __repr__(self):
        return f"Person(name = '{self.name}', age = {self.age})"

person = Person("张三", 24)

person_repr = repr(person)
# 输出 repr(person) = Person(name = '张三', age = 24)
print(f"repr(person) = {person_repr}")

# 输出 type(eval(person_repr)) = <class '__main__.Person'>
print(f"type(eval(person_repr)) = {type(eval(person_repr))}")

__hash__

重写 hash() 函数返回的内容,应该返回一个整形值。集合的元素判断使用的就是这个函数的返回结果。

示例
python
class Person:

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

    def __str__(self):
        return f"Person(name = '{self.name}', age = {self.age})"

    def __hash__(self):
        # 使得同名的 Person 对象返回相同的哈希值
        return hash(self.name)

person1 = Person("张三", 23)
person2 = Person("张三", 25)
person3 = Person("张四", 25)

# 前两行输出一定相同,后一行一定和前两行不同
print("hash(person1) = ", hash(person1))
print("hash(person2) = ", hash(person2))
print("hash(person3) = ", hash(person3))

__iter__

返回一个迭代器(iterator),可使用 for in 语法。

返回的迭代器也要实现 __iter__ 方法,返回迭代器本身,还需要 __next__ 方法返回下一个元素。

示例
python
class Student:

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

    def __str__(self):
        return self.name


class Classroom:

    def __init__(self, *names):
        self.names = [Student(name) for name in names]

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

classroom = Classroom("张三", "李四", "王五")

for student in classroom:
    print(student)

运算符重载

二元运算
运算符需要重写方法
+__add__(self, other)__radd(self, other)
-__sub__(self, other)__rsub(self, other)
*__mul__(self, other)__rmul(self, other)
**__pow__(self, other)__rpow(self, other)
pow()__pow__(self, other, modulo=None)__rpow(self, other, modulo=None)
/__truediv__(self, other)__rtruediv(self, other)
//__floordiv__(self, other)__rfloordiv(self, other)
%__mod__(self, other)__rmod(self, other)
@__matmul__(self, other)__rmatmul(self, other)
<<__lshift__(self, other)__rlshift(self, other)
>>__rshift__(self, other)__rrshift(self, other)
&__and__(self, other)__rand(self, other)
| __or__(self, other)__ror(self, other)
^ __xor__(self, other)__rxor(self, other)
  • 正常情况下:x + y => type(x).__add__(x, y)
  • 上面的抛出 NotImplemented 错误时:x + y => type(y).__radd__(y, x)
赋值运算
运算符需要重写方法
+=__iadd(self, other)__
-=__isub(self, other)__
*=__imul(self, other)__
**=__ipow(self, other)__
/=__itruediv(self, other)__
//=__ifloordiv(self, other)__
%=__imod(self, other)__
@=__imatmul(self, other)__
<<=__ilshift(self, other)__
>>=__irshift(self, other)__
&=__iand(self, other)__
|=__ior(self, other)__
^=__ixor(self, other)__

x += y => type(x).__iadd(x, y)

一元元算
运算符需要重写方法
-__neg__(self)
+__pos__(self)
~__invert__(self)
abs()__abs__(self)