Home » Как определять функции в Python 3
Как определять функции в Python 3

Как определять функции в Python 3

Работа с серверами — это постоянные скрипты, автоматизация и бесконечные повторяющиеся задачи. Функции в Python 3 — это твой спасательный круг в море рутины. Они помогут тебе избежать копипаста, сделать код читаемым и поддерживаемым. Если ты администрируешь серверы, настраиваешь хостинг или пишешь скрипты для автоматизации — знание функций критически важно. Сегодня разберём, как их правильно определять, использовать и не попасть в типичные ловушки.

Как это работает под капотом

Функции в Python — это объекты первого класса. Это значит, что их можно присваивать переменным, передавать как параметры и возвращать из других функций. Когда ты определяешь функцию через def, интерпретатор создаёт объект функции в памяти и связывает его с именем.

Вот базовая структура:

def function_name(parameters):
    """Docstring - всегда пиши документацию"""
    # Тело функции
    return result  # Опционально

Интересный факт: в Python всё является объектом, включая функции. Это означает, что у функций есть атрибуты, которые можно использовать в скриптах администрирования:

def check_server_status():
    """Проверяет статус сервера"""
    pass

print(check_server_status.__name__)  # check_server_status
print(check_server_status.__doc__)   # Проверяет статус сервера

Пошаговое руководство по определению функций

Шаг 1: Простые функции без параметров

Начнём с самого простого — функции без параметров. Отлично подходят для повторяющихся задач:

def restart_nginx():
    """Перезапускает nginx с проверкой конфигурации"""
    import subprocess
    
    # Проверяем конфигурацию
    result = subprocess.run(['nginx', '-t'], capture_output=True, text=True)
    if result.returncode == 0:
        subprocess.run(['systemctl', 'reload', 'nginx'])
        print("Nginx перезагружен успешно")
    else:
        print(f"Ошибка конфигурации: {result.stderr}")

# Использование
restart_nginx()

Шаг 2: Функции с параметрами

Параметры делают функции универсальными. Для серверного администрирования это критически важно:

def check_disk_usage(path="/", threshold=80):
    """Проверяет использование диска"""
    import shutil
    
    total, used, free = shutil.disk_usage(path)
    usage_percent = (used / total) * 100
    
    if usage_percent > threshold:
        print(f"ВНИМАНИЕ: Диск {path} заполнен на {usage_percent:.1f}%")
        return False
    else:
        print(f"Диск {path} в норме: {usage_percent:.1f}%")
        return True

# Разные способы вызова
check_disk_usage()                    # Проверка корня с порогом 80%
check_disk_usage("/var/log")         # Проверка /var/log с порогом 80%
check_disk_usage("/home", 90)        # Проверка /home с порогом 90%
check_disk_usage(threshold=95)       # Именованный параметр

Шаг 3: Значения по умолчанию и их подводные камни

Классическая ошибка новичков — изменяемые объекты как значения по умолчанию:

# НЕПРАВИЛЬНО - не делай так!
def add_server(server_name, server_list=[]):
    server_list.append(server_name)
    return server_list

# ПРАВИЛЬНО
def add_server(server_name, server_list=None):
    if server_list is None:
        server_list = []
    server_list.append(server_name)
    return server_list

# Пример использования
web_servers = add_server("nginx-01")
db_servers = add_server("mysql-01")
print(web_servers)  # ['nginx-01']
print(db_servers)   # ['mysql-01']

Продвинутые техники для серверного администрирования

*args и **kwargs

Для гибких функций администрирования используй переменное количество аргументов:

def execute_commands(*commands, **options):
    """Выполняет несколько команд с опциями"""
    import subprocess
    
    results = []
    for cmd in commands:
        try:
            result = subprocess.run(
                cmd.split(), 
                capture_output=True, 
                text=True,
                timeout=options.get('timeout', 30)
            )
            results.append({
                'command': cmd,
                'returncode': result.returncode,
                'stdout': result.stdout,
                'stderr': result.stderr
            })
        except subprocess.TimeoutExpired:
            results.append({
                'command': cmd,
                'error': 'Timeout'
            })
    
    return results

# Использование
results = execute_commands(
    "ps aux | grep nginx",
    "df -h",
    "free -m",
    timeout=60
)

Декораторы для логирования

Создай декоратор для логирования выполнения функций:

import functools
import time
from datetime import datetime

def log_execution(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        print(f"[{datetime.now()}] Запуск {func.__name__}")
        
        try:
            result = func(*args, **kwargs)
            execution_time = time.time() - start_time
            print(f"[{datetime.now()}] {func.__name__} завершена за {execution_time:.2f}с")
            return result
        except Exception as e:
            print(f"[{datetime.now()}] ОШИБКА в {func.__name__}: {e}")
            raise
    
    return wrapper

@log_execution
def backup_database(db_name):
    """Создаёт бэкап базы данных"""
    import subprocess
    
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_file = f"{db_name}_{timestamp}.sql"
    
    subprocess.run([
        'mysqldump', 
        '-u', 'root', 
        '-p', 
        db_name
    ], stdout=open(backup_file, 'w'))
    
    return backup_file

# Использование
backup_database("wordpress")

Практические примеры и кейсы

Задача Плохой подход Хороший подход
Проверка сервисов Дублирование кода для каждого сервиса Универсальная функция с параметрами
Обработка ошибок Try-catch в каждой функции Декоратор для обработки ошибок
Логирование print() в коде Декоратор или отдельная функция
Конфигурация Хардкод значений Параметры по умолчанию

Пример: Мониторинг сервера

import psutil
import requests
from typing import Dict, List, Optional

def monitor_server(
    services: List[str] = None,
    disk_paths: List[str] = None,
    memory_threshold: float = 80.0,
    cpu_threshold: float = 80.0,
    webhook_url: Optional[str] = None
) -> Dict:
    """Комплексный мониторинг сервера"""
    
    if services is None:
        services = ['nginx', 'mysql', 'redis']
    
    if disk_paths is None:
        disk_paths = ['/', '/var', '/home']
    
    results = {
        'timestamp': datetime.now().isoformat(),
        'alerts': [],
        'status': 'OK'
    }
    
    # Проверка CPU
    cpu_percent = psutil.cpu_percent(interval=1)
    if cpu_percent > cpu_threshold:
        alert = f"Высокая нагрузка CPU: {cpu_percent}%"
        results['alerts'].append(alert)
        results['status'] = 'WARNING'
    
    # Проверка памяти
    memory = psutil.virtual_memory()
    if memory.percent > memory_threshold:
        alert = f"Высокое использование памяти: {memory.percent}%"
        results['alerts'].append(alert)
        results['status'] = 'WARNING'
    
    # Проверка дисков
    for path in disk_paths:
        if check_disk_usage(path, 85):  # Используем нашу функцию
            continue
        else:
            results['alerts'].append(f"Диск {path} переполнен")
            results['status'] = 'CRITICAL'
    
    # Отправка уведомлений
    if webhook_url and results['alerts']:
        send_alert(webhook_url, results)
    
    return results

def send_alert(webhook_url: str, data: Dict) -> None:
    """Отправляет уведомление в webhook"""
    try:
        response = requests.post(
            webhook_url, 
            json=data, 
            timeout=10
        )
        response.raise_for_status()
    except requests.RequestException as e:
        print(f"Ошибка отправки уведомления: {e}")

# Использование
monitor_result = monitor_server(
    services=['nginx', 'mysql'],
    webhook_url="https://hooks.slack.com/your/webhook/url"
)

Лямбда-функции и их место в администрировании

Лямбда-функции отлично подходят для простых операций фильтрации и сортировки:

import os
from datetime import datetime

def get_log_files(directory="/var/log", days_old=7):
    """Получает файлы логов старше N дней"""
    
    files = []
    for filename in os.listdir(directory):
        filepath = os.path.join(directory, filename)
        if os.path.isfile(filepath):
            stat = os.stat(filepath)
            files.append({
                'name': filename,
                'size': stat.st_size,
                'modified': stat.st_mtime,
                'path': filepath
            })
    
    # Фильтрация с помощью lambda
    old_files = list(filter(
        lambda f: (time.time() - f['modified']) > (days_old * 24 * 3600),
        files
    ))
    
    # Сортировка по размеру
    old_files.sort(key=lambda f: f['size'], reverse=True)
    
    return old_files

# Использование
old_logs = get_log_files("/var/log", 30)
for log in old_logs[:5]:  # Топ 5 самых больших старых файлов
    print(f"{log['name']}: {log['size']} байт")

Генераторы для работы с большими данными

При работе с логами и большими файлами используй генераторы:

def parse_access_log(filepath):
    """Генератор для парсинга access.log"""
    
    with open(filepath, 'r') as file:
        for line in file:
            parts = line.strip().split()
            if len(parts) >= 7:
                yield {
                    'ip': parts[0],
                    'timestamp': parts[3][1:],  # Убираем [
                    'method': parts[5][1:],     # Убираем "
                    'url': parts[6],
                    'status': parts[8],
                    'size': parts[9]
                }

def analyze_traffic(log_file):
    """Анализ трафика без загрузки всего файла в память"""
    
    ip_stats = {}
    status_stats = {}
    
    for entry in parse_access_log(log_file):
        # Статистика по IP
        ip_stats[entry['ip']] = ip_stats.get(entry['ip'], 0) + 1
        
        # Статистика по статусам
        status_stats[entry['status']] = status_stats.get(entry['status'], 0) + 1
    
    return {
        'top_ips': sorted(ip_stats.items(), key=lambda x: x[1], reverse=True)[:10],
        'status_codes': status_stats
    }

# Использование
stats = analyze_traffic("/var/log/nginx/access.log")
print("Топ IP-адресов:")
for ip, count in stats['top_ips']:
    print(f"{ip}: {count} запросов")

Type hints и документация

Современный Python требует типизации. Это особенно важно для серверных скриптов:

from typing import Dict, List, Optional, Union, Tuple
import subprocess

def execute_command(
    command: str,
    timeout: int = 30,
    check_return_code: bool = True
) -> Tuple[int, str, str]:
    """
    Выполняет команду в shell
    
    Args:
        command: Команда для выполнения
        timeout: Таймаут в секундах
        check_return_code: Проверять ли код возврата
        
    Returns:
        Кортеж (код_возврата, stdout, stderr)
        
    Raises:
        subprocess.TimeoutExpired: При превышении таймаута
        subprocess.CalledProcessError: При ошибке выполнения
    """
    
    result = subprocess.run(
        command.split(),
        capture_output=True,
        text=True,
        timeout=timeout,
        check=check_return_code
    )
    
    return result.returncode, result.stdout, result.stderr

def get_system_info() -> Dict[str, Union[str, int, float]]:
    """Получает информацию о системе"""
    
    import platform
    import psutil
    
    return {
        'hostname': platform.node(),
        'os': platform.system(),
        'python_version': platform.python_version(),
        'cpu_count': psutil.cpu_count(),
        'memory_total': psutil.virtual_memory().total,
        'disk_usage': psutil.disk_usage('/').percent
    }

Автоматизация и интеграция

Функции можно легко интегрировать с различными инструментами автоматизации:

Интеграция с Cron

#!/usr/bin/env python3
"""
Скрипт для cron: ежедневная проверка сервера
Добавь в crontab: 0 */6 * * * /usr/local/bin/server_check.py
"""

import sys
import json
from pathlib import Path

def main():
    """Основная функция для cron"""
    
    try:
        # Загрузка конфигурации
        config_path = Path('/etc/server-monitor/config.json')
        if config_path.exists():
            with open(config_path) as f:
                config = json.load(f)
        else:
            config = {
                'memory_threshold': 80,
                'disk_threshold': 85,
                'services': ['nginx', 'mysql'],
                'webhook_url': None
            }
        
        # Мониторинг
        results = monitor_server(
            memory_threshold=config['memory_threshold'],
            webhook_url=config.get('webhook_url')
        )
        
        # Логирование
        log_path = Path('/var/log/server-monitor.log')
        with open(log_path, 'a') as f:
            f.write(f"{results['timestamp']}: {results['status']}\n")
            for alert in results['alerts']:
                f.write(f"  ALERT: {alert}\n")
        
        # Код возврата для cron
        sys.exit(0 if results['status'] == 'OK' else 1)
        
    except Exception as e:
        print(f"Ошибка: {e}", file=sys.stderr)
        sys.exit(2)

if __name__ == '__main__':
    main()

Интеграция с Ansible

Функции можно использовать в кастомных модулях Ansible:

def ansible_module_runner():
    """Функция для использования в Ansible модуле"""
    
    from ansible.module_utils.basic import AnsibleModule
    
    module = AnsibleModule(
        argument_spec=dict(
            service=dict(required=True, type='str'),
            state=dict(default='started', choices=['started', 'stopped', 'restarted']),
            timeout=dict(default=30, type='int')
        )
    )
    
    service = module.params['service']
    state = module.params['state']
    timeout = module.params['timeout']
    
    try:
        if state == 'started':
            returncode, stdout, stderr = execute_command(
                f"systemctl start {service}",
                timeout=timeout
            )
        elif state == 'stopped':
            returncode, stdout, stderr = execute_command(
                f"systemctl stop {service}",
                timeout=timeout
            )
        elif state == 'restarted':
            returncode, stdout, stderr = execute_command(
                f"systemctl restart {service}",
                timeout=timeout
            )
        
        module.exit_json(
            changed=True,
            msg=f"Service {service} {state}",
            stdout=stdout,
            stderr=stderr
        )
        
    except Exception as e:
        module.fail_json(msg=str(e))

Производительность и оптимизация

Техника Когда использовать Пример
Кэширование Частые вызовы с одинаковыми параметрами @lru_cache для DNS резолвинга
Генераторы Обработка больших файлов Парсинг логов
Мультипроцессинг CPU-интенсивные задачи Анализ нескольких логов
Асинхронность I/O операции Проверка множества серверов

Кэширование результатов

from functools import lru_cache
import socket

@lru_cache(maxsize=128)
def resolve_hostname(hostname: str) -> str:
    """Резолвит hostname с кэшированием"""
    try:
        return socket.gethostbyname(hostname)
    except socket.gaierror:
        return None

# Первый вызов - медленный
ip1 = resolve_hostname("google.com")
# Второй вызов - мгновенный из кэша
ip2 = resolve_hostname("google.com")

Тестирование функций

Всегда тестируй свои функции, особенно если они управляют серверами:

import unittest
from unittest.mock import patch, MagicMock

class TestServerFunctions(unittest.TestCase):
    
    @patch('subprocess.run')
    def test_execute_command_success(self, mock_run):
        """Тест успешного выполнения команды"""
        
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="Success",
            stderr=""
        )
        
        returncode, stdout, stderr = execute_command("ls -la")
        
        self.assertEqual(returncode, 0)
        self.assertEqual(stdout, "Success")
        mock_run.assert_called_once()
    
    @patch('psutil.virtual_memory')
    def test_monitor_server_memory_alert(self, mock_memory):
        """Тест алерта по памяти"""
        
        mock_memory.return_value = MagicMock(percent=85.0)
        
        results = monitor_server(memory_threshold=80.0)
        
        self.assertEqual(results['status'], 'WARNING')
        self.assertTrue(any('памяти' in alert for alert in results['alerts']))

if __name__ == '__main__':
    unittest.main()

Современные возможности Python 3.8+

Walrus Operator (:=)

def check_large_files(directory: str, size_limit: int = 100 * 1024 * 1024):
    """Проверяет наличие больших файлов"""
    
    large_files = []
    
    for filename in os.listdir(directory):
        filepath = os.path.join(directory, filename)
        if os.path.isfile(filepath) and (size := os.path.getsize(filepath)) > size_limit:
            large_files.append({
                'name': filename,
                'size': size,
                'size_mb': size / (1024 * 1024)
            })
    
    return large_files

Positional-only и keyword-only параметры

def configure_service(service_name, /, *, config_path=None, restart=True, timeout=30):
    """
    Настраивает сервис
    
    Args:
        service_name: Имя сервиса (только позиционный)
        config_path: Путь к конфигурации (только keyword)
        restart: Перезапускать ли сервис (только keyword)
        timeout: Таймаут операции (только keyword)
    """
    
    # service_name можно передать только позиционно
    # остальные параметры - только по имени
    
    print(f"Настраиваем {service_name}")
    if config_path:
        print(f"Конфигурация: {config_path}")
    
    if restart:
        execute_command(f"systemctl restart {service_name}", timeout=timeout)

# Правильное использование
configure_service("nginx", config_path="/etc/nginx/nginx.conf", restart=True)

# Неправильное использование (вызовет ошибку)
# configure_service("nginx", "/etc/nginx/nginx.conf", True)

Полезные ресурсы

Для углубленного изучения рекомендую:

Для тестирования и разработки серверных скриптов понадобится надёжная инфраструктура. Рекомендую арендовать VPS для экспериментов или выделенный сервер для продакшена.

Заключение и рекомендации

Функции в Python 3 — это не просто способ организации кода, а мощный инструмент для автоматизации серверного администрирования. Основные принципы:

  • Делай функции маленькими и специализированными — одна функция = одна задача
  • Используй типизацию — это сэкономит часы отладки
  • Пиши документацию — через полгода ты забудешь, что делает твой код
  • Обрабатывай ошибки — серверы любят ломаться в самый неподходящий момент
  • Тестируй критически важные функции — автоматизация должна быть надёжной

Начни с простых функций для повторяющихся задач: проверка сервисов, мониторинг ресурсов, бэкапы. Постепенно добавляй сложность: декораторы, генераторы, асинхронность. Помни: лучше написать десять простых функций, чем одну сложную.

Функции в Python открывают дорогу к продвинутой автоматизации: от простых скриптов до сложных систем оркестрации. Главное — не бойся экспериментировать и всегда держи в голове принцип DRY (Don’t Repeat Yourself). Твои будущие коллеги (и ты сам через полгода) скажут спасибо за читаемый и поддерживаемый код.


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked