- Home »

Как определять функции в 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)
Полезные ресурсы
Для углубленного изучения рекомендую:
- Официальная документация Python — исчерпывающий гайд по функциям
- Real Python — отличные примеры и лучшие практики
- CPython на GitHub — исходный код интерпретатора
Для тестирования и разработки серверных скриптов понадобится надёжная инфраструктура. Рекомендую арендовать VPS для экспериментов или выделенный сервер для продакшена.
Заключение и рекомендации
Функции в Python 3 — это не просто способ организации кода, а мощный инструмент для автоматизации серверного администрирования. Основные принципы:
- Делай функции маленькими и специализированными — одна функция = одна задача
- Используй типизацию — это сэкономит часы отладки
- Пиши документацию — через полгода ты забудешь, что делает твой код
- Обрабатывай ошибки — серверы любят ломаться в самый неподходящий момент
- Тестируй критически важные функции — автоматизация должна быть надёжной
Начни с простых функций для повторяющихся задач: проверка сервисов, мониторинг ресурсов, бэкапы. Постепенно добавляй сложность: декораторы, генераторы, асинхронность. Помни: лучше написать десять простых функций, чем одну сложную.
Функции в Python открывают дорогу к продвинутой автоматизации: от простых скриптов до сложных систем оркестрации. Главное — не бойся экспериментировать и всегда держи в голове принцип DRY (Don’t Repeat Yourself). Твои будущие коллеги (и ты сам через полгода) скажут спасибо за читаемый и поддерживаемый код.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.