Python'da Cron benzeri bir zamanlayıcı nasıl edinebilirim? [kapalı]


348

Ben işlevselliği sağlayacak atve crongibi Python bir kütüphane arıyorum .

Kutuda yüklü araçlara güvenmek yerine saf bir Python çözümüne sahip olmak istiyorum; bu şekilde cronsuz makinelerde koşuyorum.

Bilmeyenler için cron: aşağıdaki gibi bir ifadeye dayalı görevleri zamanlayabilirsiniz:

 0 2 * * 7 /usr/bin/run-backup # run the backups at 0200 on Every Sunday
 0 9-17/2 * * 1-5 /usr/bin/purge-temps # run the purge temps command, every 2 hours between 9am and 5pm on Mondays to Fridays.

Cron zaman ifadesi sözdizimi daha az önemlidir, ancak bu tür esnekliğe sahip bir şey istiyorum.

Bunu benim için hazır yapan bir şey yoksa, yapı taşlarının böyle bir şey yapmaları için herhangi bir öneri minnetle alınır.

Düzenle İşlemleri başlatmakla ilgilenmiyorum, sadece "işler" de Python - python fonksiyonları ile yazılmış. Zorunlu olarak, bunun farklı bir iş parçacığı olacağını düşünüyorum, ancak farklı bir süreçte değil.

Bu amaçla, cron zaman ifadesinin ifadesini arıyorum, ama Python'da.

Cron gelmiştir yıllardır civarında olmuştur ama mümkün olduğunca taşınabilir olarak davranmaya çalışıyorum. Onun varlığına güvenemiyorum.


1
Bunun nasıl yapılacağını da bilmek istiyorum. Çapraz platform çözümüne sahip olmak, platforma özgü bileşenlere bağlı olmaktan daha yararlı olacaktır.
Sean

7
Bu konu dışı değil, bu çok önemli ve yararlı bir soru
Connor

Yanıtlar:


571

Hafif bir ödeme programı arıyorsanız :

import schedule
import time

def job():
    print("I'm working...")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)

while 1:
    schedule.run_pending()
    time.sleep(1)

Açıklama : O kütüphanenin yazarıyım.


7
Bakıcı olduğunuzu belirtmelisiniz schedule. Benim için iyi çalıştı. Sözdizimi ve desteklenen dekoratörler gibi cron'a sahip olsaydı daha da güzel olurdu (crython'a bakın, ancak işe yaramadığı için bu kütüphaneyi kullanmayın; zamanlama iyi yazılmış gibi görünmüyor).
Tim Ludwinski

23
İşe bir parametre iletmenin bir yolu var mı? Bunun gibi bir şey yapmak istiyorum: schedule.every (). Hour.do (job (myParam))
Zen Skunkworx

5
schedule.every (). hour.do (job) bu ​​her saatte bir çalışıyor mu? 01:00, 02:00, 03:00 gibi. başlangıç ​​saati tam saat olmasa bile?
swateek

1
bu kodun scheduler.py içinde olduğunu varsayalım. bu kod otomatik olarak çalışacak mı?
Kishan

26
@ darrel-Holt ve @ zen skunkworx: do()ileri işlevi iş işlevine kendisine iletmek ekstra argümanlar: schedule.readthedocs.io/en/stable/api.html#schedule.Job.do Örneğin, yapabileceğiniz bu : schedule.every().hour.do(job, param1, param2)Lambda kullanmaya gerek yok. Umarım bu yardımcı olur :)
dbader

65

Crontab'ınızı belirtmek için normal Python argümanını geçen sözdizimini kullanabilirsiniz. Örneğin, bir Event sınıfını aşağıdaki gibi tanımladığımızı varsayalım:

from datetime import datetime, timedelta
import time

# Some utility classes / functions first
class AllMatch(set):
    """Universal set - match everything"""
    def __contains__(self, item): return True

allMatch = AllMatch()

def conv_to_set(obj):  # Allow single integer to be provided
    if isinstance(obj, (int,long)):
        return set([obj])  # Single item
    if not isinstance(obj, set):
        obj = set(obj)
    return obj

# The actual Event class
class Event(object):
    def __init__(self, action, min=allMatch, hour=allMatch, 
                       day=allMatch, month=allMatch, dow=allMatch, 
                       args=(), kwargs={}):
        self.mins = conv_to_set(min)
        self.hours= conv_to_set(hour)
        self.days = conv_to_set(day)
        self.months = conv_to_set(month)
        self.dow = conv_to_set(dow)
        self.action = action
        self.args = args
        self.kwargs = kwargs

    def matchtime(self, t):
        """Return True if this event should trigger at the specified datetime"""
        return ((t.minute     in self.mins) and
                (t.hour       in self.hours) and
                (t.day        in self.days) and
                (t.month      in self.months) and
                (t.weekday()  in self.dow))

    def check(self, t):
        if self.matchtime(t):
            self.action(*self.args, **self.kwargs)

(Not: İyice test edilmemiştir)

Daha sonra CronTab'ınız normal python sözdiziminde şu şekilde belirtilebilir:

c = CronTab(
  Event(perform_backup, 0, 2, dow=6 ),
  Event(purge_temps, 0, range(9,18,2), dow=range(0,5))
)

Bu şekilde Python'un argüman mekaniğinin tam gücünü elde edersiniz (konum ve anahtar kelime argümanlarını karıştırır ve haftalar ve aylar için sembolik isimler kullanabilirsiniz)

CronTab sınıfı, yalnızca küçük artışlarla uyumak ve her olayda check () öğesini çağırmak olarak tanımlanır. (Muhtemelen gün ışığından yararlanma saati / zaman dilimlerine karşı dikkatli olunması gereken bazı incelikler vardır). İşte hızlı bir uygulama:

class CronTab(object):
    def __init__(self, *events):
        self.events = events

    def run(self):
        t=datetime(*datetime.now().timetuple()[:5])
        while 1:
            for e in self.events:
                e.check(t)

            t += timedelta(minutes=1)
            while datetime.now() < t:
                time.sleep((t - datetime.now()).seconds)

Dikkat edilmesi gereken birkaç nokta: Python'un hafta içi / ayları sıfır endekslidir (cron'dan farklı olarak) ve bu aralık son öğeyi hariç tutar, bu nedenle "1-5" gibi sözdizimi aralık (0,5) - yani [0,1,2, 3,4]. Cron sözdizimini tercih ediyorsanız, ayrıştırmak çok zor olmamalıdır.


Deneyimsiz olanlar için bazı içe aktarma ifadeleri eklemek isteyebilirsiniz. Ben tüm sınıfları zaman ithalat uykusundan datetime ithalat * ile tek bir dosyaya koyarak sona erdi ve time.sleep uyku değişti. Güzel, basit ve zarif bir çözüm. Teşekkürler.
mavnn

1
Merak ediyorum, bu neden Kronos'a tercih ediliyor? Sched is buggy (kronos sched kullandığından)? Yoksa bu sadece eskimiş mi?
cregox

Teşekkürler brian, çözümünüzü üretimde kullanıyorum ve oldukça iyi çalışıyor. Ancak, diğerlerinin de belirttiği gibi, çalıştırma kodunuzda ince bir hata var. Ayrıca ihtiyaçları için aşırı karmaşık buldum.
raph.amiard

1
Bu harika, ama yine de her saat, dakika, vb yürütmek için eğik çizgi notasyonunu desteklemiyor ...
Chris Koston

1
Kendi sınıflarınızı yazmak için mükemmel bir fikir, örneğin bir sunucuda sudo erişimim olmadığında ve bu yüzden yapamadığınızda pip install anything:)
Cometsong


27

Aramalarımda gördüğüm bir schedşey, aradığınız bir şey olabilecek python'un modülü.


11
sched artık mutlak zamanlama yapan enterabs () yöntemine sahiptir.
Jerther

5
Zamanlama şimdi python2 ve 3 stdlib bir parçası olduğu ve mutlak zamanlama yaptığı için bu tercih edilen cevap olmasını beklenir.
Michael


11

Yukarıdakiler aşağı yukarı aynı ama aynı zamanda gevent kullanarak :)

"""Gevent based crontab implementation"""

from datetime import datetime, timedelta
import gevent

# Some utility classes / functions first
def conv_to_set(obj):
    """Converts to set allowing single integer to be provided"""

    if isinstance(obj, (int, long)):
        return set([obj])  # Single item
    if not isinstance(obj, set):
        obj = set(obj)
    return obj

class AllMatch(set):
    """Universal set - match everything"""
    def __contains__(self, item): 
        return True

allMatch = AllMatch()

class Event(object):
    """The Actual Event Class"""

    def __init__(self, action, minute=allMatch, hour=allMatch, 
                       day=allMatch, month=allMatch, daysofweek=allMatch, 
                       args=(), kwargs={}):
        self.mins = conv_to_set(minute)
        self.hours = conv_to_set(hour)
        self.days = conv_to_set(day)
        self.months = conv_to_set(month)
        self.daysofweek = conv_to_set(daysofweek)
        self.action = action
        self.args = args
        self.kwargs = kwargs

    def matchtime(self, t1):
        """Return True if this event should trigger at the specified datetime"""
        return ((t1.minute     in self.mins) and
                (t1.hour       in self.hours) and
                (t1.day        in self.days) and
                (t1.month      in self.months) and
                (t1.weekday()  in self.daysofweek))

    def check(self, t):
        """Check and run action if needed"""

        if self.matchtime(t):
            self.action(*self.args, **self.kwargs)

class CronTab(object):
    """The crontab implementation"""

    def __init__(self, *events):
        self.events = events

    def _check(self):
        """Check all events in separate greenlets"""

        t1 = datetime(*datetime.now().timetuple()[:5])
        for event in self.events:
            gevent.spawn(event.check, t1)

        t1 += timedelta(minutes=1)
        s1 = (t1 - datetime.now()).seconds + 1
        print "Checking again in %s seconds" % s1
        job = gevent.spawn_later(s1, self._check)

    def run(self):
        """Run the cron forever"""

        self._check()
        while True:
            gevent.sleep(60)

import os 
def test_task():
    """Just an example that sends a bell and asd to all terminals"""

    os.system('echo asd | wall')  

cron = CronTab(
  Event(test_task, 22, 1 ),
  Event(test_task, 0, range(9,18,2), daysofweek=range(0,5)),
)
cron.run()

Datetime.timetuple () 'nin yıl, ay, gün ... ile başlayacağına dikkat edin.
Trey Stout

9

Listelenen çözümlerin hiçbiri karmaşık bir cron zamanlama dizesini ayrıştırmaya çalışmaz. İşte benim versiyonum, croniter kullanıyor . Temel öz:

schedule = "*/5 * * * *" # Run every five minutes

nextRunTime = getNextCronRunTime(schedule)
while True:
     roundedDownTime = roundDownTime()
     if (roundedDownTime == nextRunTime):
         ####################################
         ### Do your periodic thing here. ###
         ####################################
         nextRunTime = getNextCronRunTime(schedule)
     elif (roundedDownTime > nextRunTime):
         # We missed an execution. Error. Re initialize.
         nextRunTime = getNextCronRunTime(schedule)
     sleepTillTopOfNextMinute()

Yardımcı rutinler:

from croniter import croniter
from datetime import datetime, timedelta

# Round time down to the top of the previous minute
def roundDownTime(dt=None, dateDelta=timedelta(minutes=1)):
    roundTo = dateDelta.total_seconds()
    if dt == None : dt = datetime.now()
    seconds = (dt - dt.min).seconds
    rounding = (seconds+roundTo/2) // roundTo * roundTo
    return dt + timedelta(0,rounding-seconds,-dt.microsecond)

# Get next run time from now, based on schedule specified by cron string
def getNextCronRunTime(schedule):
    return croniter(schedule, datetime.now()).get_next(datetime)

# Sleep till the top of the next minute
def sleepTillTopOfNextMinute():
    t = datetime.utcnow()
    sleeptime = 60 - (t.second + t.microsecond/1000000.0)
    time.sleep(sleeptime)

Birisi nasıl "bir infazı kaçırdı" elif? Atm "Periyodik bir şey yap" 1 dakikadan "* * * * *"biraz time.sleepdaha fazla eklemek gibi bir zaman çizelgesi kullanıyorum if, ama ben her zaman if if şeyler şeyler görüyorum. 1 dakikadan fazla sürdüğünde, sadece döngü döngüsünün eksik döngü yürütmesini atladığını görüyorum.
TPPZ

@TPPZ İşlem askıya alınmış olabilir, saat manuel olarak veya ntp vb. İle değiştirilmiş olabilir. Croniter Airflow'da kullanılır ve Crontab modülünden ve diğerlerinden daha tam özellikli görünüyor.
dlamblin

7

Senaryoyu değiştirdim.

  1. Kullanımı kolay:

    cron = Cron()
    cron.add('* * * * *'   , minute_task) # every minute
    cron.add('33 * * * *'  , day_task)    # every hour
    cron.add('34 18 * * *' , day_task)    # every day
    cron.run()
  2. Görevi bir dakikanın ilk saniyesinde başlatmaya çalışın.

Github Kodu


6

Brian tarafından önerilen CronTab sınıfı çalışma yöntemi için küçük bir düzeltme var .

Zamanlama, her dakikanın sonunda bir saniyelik, sert bir döngüye yol açan bir saniye kadar sürdü.

class CronTab(object):
    def __init__(self, *events):
        self.events = events

    def run(self):
        t=datetime(*datetime.now().timetuple()[:5])
        while 1:
            for e in self.events:
                e.check(t)

            t += timedelta(minutes=1)
            n = datetime.now()
            while n < t:
                s = (t - n).seconds + 1
                time.sleep(s)
                n = datetime.now()

4

Bunu yapmak için bir "saf python" yolu yoktur çünkü çözümünüzü çalıştırmak için başka bir işlem python başlatmak zorunda kalacaktı. Her platformun süreçleri başlatmak ve ilerlemelerini izlemek için bir veya yirmi farklı yolu olacaktır. Unix platformlarında cron eski standarttır. Mac OS X'te, cron benzeri başlatmayı, istediğiniz şeyse işleminizi canlı tutabilecek bekçi işlevselliği ile birleştiren launchd de var. Python çalıştıktan sonra görevleri zamanlamak için sched modülünü kullanabilirsiniz.


4

Çok yanıt olduğunu biliyorum, ama başka bir çözüm dekoratörler ile gitmek olabilir . Bu, bir işlevi her gün belirli bir saatte tekrarlamak için bir örnektir. Bu şekilde kullanmakla ilgili harika düşünce, sadece Syntactic Sugar'u programlamak istediğiniz işleve eklemeniz gerektiğidir :

@repeatEveryDay(hour=6, minutes=30)
def sayHello(name):
    print(f"Hello {name}")

sayHello("Bob") # Now this function will be invoked every day at 6.30 a.m

Ve dekoratör şöyle görünecek:

def repeatEveryDay(hour, minutes=0, seconds=0):
    """
    Decorator that will run the decorated function everyday at that hour, minutes and seconds.
    :param hour: 0-24
    :param minutes: 0-60 (Optional)
    :param seconds: 0-60 (Optional)
    """
    def decoratorRepeat(func):

        @functools.wraps(func)
        def wrapperRepeat(*args, **kwargs):

            def getLocalTime():
                return datetime.datetime.fromtimestamp(time.mktime(time.localtime()))

            # Get the datetime of the first function call
            td = datetime.timedelta(seconds=15)
            if wrapperRepeat.nextSent == None:
                now = getLocalTime()
                wrapperRepeat.nextSent = datetime.datetime(now.year, now.month, now.day, hour, minutes, seconds)
                if wrapperRepeat.nextSent < now:
                    wrapperRepeat.nextSent += td

            # Waiting till next day
            while getLocalTime() < wrapperRepeat.nextSent:
                time.sleep(1)

            # Call the function
            func(*args, **kwargs)

            # Get the datetime of the next function call
            wrapperRepeat.nextSent += td
            wrapperRepeat(*args, **kwargs)

        wrapperRepeat.nextSent = None
        return wrapperRepeat

    return decoratorRepeat

1

Brian'ın çözümü gayet iyi çalışıyor. Ancak, diğerlerinin de belirttiği gibi, çalışma kodunda ince bir hata var. Ayrıca ihtiyaçları için aşırı karmaşık buldum.

İşte herhangi birinin ihtiyacı olması durumunda çalıştırma kodu için daha basit ve fonksiyonel alternatifim:

def run(self):
    while 1:
        t = datetime.now()
        for e in self.events:
            e.check(t)

        time.sleep(60 - t.second - t.microsecond / 1000000.0)

1

Bir başka önemsiz çözüm şöyle olurdu:

from aqcron import At
from time import sleep
from datetime import datetime

# Event scheduling
event_1 = At( second=5 )
event_2 = At( second=[0,20,40] )

while True:
    now = datetime.now()

    # Event check
    if now in event_1: print "event_1"
    if now in event_2: print "event_2"

    sleep(1)

Ve aqcron sınıfı.

# aqcron.py

class At(object):
    def __init__(self, year=None,    month=None,
                 day=None,     weekday=None,
                 hour=None,    minute=None,
                 second=None):
        loc = locals()
        loc.pop("self")
        self.at = dict((k, v) for k, v in loc.iteritems() if v != None)

    def __contains__(self, now):
        for k in self.at.keys():
            try:
                if not getattr(now, k) in self.at[k]: return False
            except TypeError:
                if self.at[k] != getattr(now, k): return False
        return True

1
Birden fazla soruya kopyala ve yapıştır levhası / kelimesi kelimesine yanıtlar gönderirken dikkatli olun, bunlar topluluk tarafından "spam" olarak işaretlenir. Bunu yapıyorsanız, bu genellikle soruların kopyaları olduğu anlamına gelir, bu yüzden onları şu şekilde işaretleyin: stackoverflow.com/a/12360556/419
Kev


0

Böyle bir şeyin zaten var olup olmadığını bilmiyorum. Kendinizi zaman, tarih ve / veya takvim modülleriyle yazmak kolaydır, bkz. Http://docs.python.org/library/time.html

Bir piton çözüm için tek endişesi işinizi ihtiyaçları çalıştıran her zaman olmak ve muhtemelen otomatik yeniden başlatma, kendisi için bir şey sonra "dirildi" olmak olduğunu yapmak sistem bağımlı çözümlere duyulan gereksinimi.


3
Kendinizi yuvarlamak bir seçenektir - en iyi kod yazmak zorunda olmadığınız kod olsa da. Diriliş, sanırım dikkate almam gereken bir şey.
jamesh


0

Sunucuda Crontab Yöntemi.

Python dosya adı hello.py

Adım 1: Bir sh dosyası oluşturun s.sh adını verelim

python3 /home/ubuntu/Shaurya/Folder/hello.py> /home/ubuntu/Shaurya/Folder/log.txt 2> & 1

Adım2: Crontab Düzenleyiciyi Aç

crontab -e

Adım3: Program Zamanını Ekleyin

Crontab Biçimlendirmesini Kullan

2 * * * * sudo sh /home/ubuntu/Shaurya/Folder/s.sh

Bu cron “2. dakikada” çalışacak.


0

Pycron paketinin bu sorunu nasıl çözdüğünü seviyorum .

import pycron
import time

while True:
    if pycron.is_now('0 2 * * 0'):   # True Every Sunday at 02:00
        print('running backup')
    time.sleep(60)

1
Bu iyi bir fikir değildir, çünkü "print ('yedek yedekleme')" kodunuz 5 sn aralıklarla tüm dakikayı başlatır. Bu durumda gecikme 60 saniye olmalıdır.
n158

Haklısın! Bunu işaret ettiğiniz için teşekkürler.
Duffau
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.